@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
package/docs/tree.md ADDED
@@ -0,0 +1,197 @@
1
+ # Session Tree Navigation
2
+
3
+ The `/tree` command provides tree-based navigation of the session history.
4
+
5
+ ## Overview
6
+
7
+ Sessions are stored as trees where each entry has an `id` and `parentId`. The "leaf" pointer tracks the current position. `/tree` lets you navigate to any point and optionally summarize the branch you're leaving.
8
+
9
+ ### Comparison with `/branch`
10
+
11
+ | Feature | `/branch` | `/tree` |
12
+ |---------|-----------|---------|
13
+ | View | Flat list of user messages | Full tree structure |
14
+ | Action | Extracts path to **new session file** | Changes leaf in **same session** |
15
+ | Summary | Never | Optional (user prompted) |
16
+ | Events | `session_before_branch` / `session_branch` | `session_before_tree` / `session_tree` |
17
+
18
+ ## Tree UI
19
+
20
+ ```
21
+ ├─ user: "Hello, can you help..."
22
+ │ └─ assistant: "Of course! I can..."
23
+ │ ├─ user: "Let's try approach A..."
24
+ │ │ └─ assistant: "For approach A..."
25
+ │ │ └─ [compaction: 12k tokens]
26
+ │ │ └─ user: "That worked..." ← active
27
+ │ └─ user: "Actually, approach B..."
28
+ │ └─ assistant: "For approach B..."
29
+ ```
30
+
31
+ ### Controls
32
+
33
+ | Key | Action |
34
+ |-----|--------|
35
+ | ↑/↓ | Navigate (depth-first order) |
36
+ | Enter | Select node |
37
+ | Escape/Ctrl+C | Cancel |
38
+ | Ctrl+U | Toggle: user messages only |
39
+ | Ctrl+O | Toggle: show all (including custom/label entries) |
40
+
41
+ ### Display
42
+
43
+ - Height: half terminal height
44
+ - Current leaf marked with `← active`
45
+ - Labels shown inline: `[label-name]`
46
+ - Default filter hides `label` and `custom` entries (shown in Ctrl+O mode)
47
+ - Children sorted by timestamp (oldest first)
48
+
49
+ ## Selection Behavior
50
+
51
+ ### User Message or Custom Message
52
+ 1. Leaf set to **parent** of selected node (or `null` if root)
53
+ 2. Message text placed in **editor** for re-submission
54
+ 3. User edits and submits, creating a new branch
55
+
56
+ ### Non-User Message (assistant, compaction, etc.)
57
+ 1. Leaf set to **selected node**
58
+ 2. Editor stays empty
59
+ 3. User continues from that point
60
+
61
+ ### Selecting Root User Message
62
+ If user selects the very first message (has no parent):
63
+ 1. Leaf reset to `null` (empty conversation)
64
+ 2. Message text placed in editor
65
+ 3. User effectively restarts from scratch
66
+
67
+ ## Branch Summarization
68
+
69
+ When switching, user is prompted: "Summarize the branch you're leaving?"
70
+
71
+ ### What Gets Summarized
72
+
73
+ Path from old leaf back to common ancestor with target:
74
+
75
+ ```
76
+ A → B → C → D → E → F ← old leaf
77
+ ↘ G → H ← target
78
+ ```
79
+
80
+ Abandoned path: D → E → F (summarized)
81
+
82
+ Summarization stops at:
83
+ 1. Common ancestor (always)
84
+ 2. Compaction node (if encountered first)
85
+
86
+ ### Summary Storage
87
+
88
+ Stored as `BranchSummaryEntry`:
89
+
90
+ ```typescript
91
+ interface BranchSummaryEntry {
92
+ type: "branch_summary";
93
+ id: string;
94
+ parentId: string; // New leaf position
95
+ timestamp: string;
96
+ fromId: string; // Old leaf we abandoned
97
+ summary: string; // LLM-generated summary
98
+ details?: unknown; // Optional hook data
99
+ }
100
+ ```
101
+
102
+ ## Implementation
103
+
104
+ ### AgentSession.navigateTree()
105
+
106
+ ```typescript
107
+ async navigateTree(
108
+ targetId: string,
109
+ options?: { summarize?: boolean; customInstructions?: string }
110
+ ): Promise<{ editorText?: string; cancelled: boolean }>
111
+ ```
112
+
113
+ Flow:
114
+ 1. Validate target, check no-op (target === current leaf)
115
+ 2. Find common ancestor between old leaf and target
116
+ 3. Collect entries to summarize (if requested)
117
+ 4. Fire `session_before_tree` event (hook can cancel or provide summary)
118
+ 5. Run default summarizer if needed
119
+ 6. Switch leaf via `branch()` or `branchWithSummary()`
120
+ 7. Update agent: `agent.replaceMessages(sessionManager.buildSessionContext().messages)`
121
+ 8. Fire `session_tree` event
122
+ 9. Notify custom tools via session event
123
+ 10. Return result with `editorText` if user message was selected
124
+
125
+ ### SessionManager
126
+
127
+ - `getLeafUuid(): string | null` - Current leaf (null if empty)
128
+ - `resetLeaf(): void` - Set leaf to null (for root user message navigation)
129
+ - `getTree(): SessionTreeNode[]` - Full tree with children sorted by timestamp
130
+ - `branch(id)` - Change leaf pointer
131
+ - `branchWithSummary(id, summary)` - Change leaf and create summary entry
132
+
133
+ ### InteractiveMode
134
+
135
+ `/tree` command shows `TreeSelectorComponent`, then:
136
+ 1. Prompt for summarization
137
+ 2. Call `session.navigateTree()`
138
+ 3. Clear and re-render chat
139
+ 4. Set editor text if applicable
140
+
141
+ ## Hook Events
142
+
143
+ ### `session_before_tree`
144
+
145
+ ```typescript
146
+ interface TreePreparation {
147
+ targetId: string;
148
+ oldLeafId: string | null;
149
+ commonAncestorId: string | null;
150
+ entriesToSummarize: SessionEntry[];
151
+ userWantsSummary: boolean;
152
+ }
153
+
154
+ interface SessionBeforeTreeEvent {
155
+ type: "session_before_tree";
156
+ preparation: TreePreparation;
157
+ model: Model;
158
+ signal: AbortSignal;
159
+ }
160
+
161
+ interface SessionBeforeTreeResult {
162
+ cancel?: boolean;
163
+ summary?: { summary: string; details?: unknown };
164
+ }
165
+ ```
166
+
167
+ ### `session_tree`
168
+
169
+ ```typescript
170
+ interface SessionTreeEvent {
171
+ type: "session_tree";
172
+ newLeafId: string | null;
173
+ oldLeafId: string | null;
174
+ summaryEntry?: BranchSummaryEntry;
175
+ fromHook?: boolean;
176
+ }
177
+ ```
178
+
179
+ ### Example: Custom Summarizer
180
+
181
+ ```typescript
182
+ export default function(pi: HookAPI) {
183
+ pi.on("session_before_tree", async (event, ctx) => {
184
+ if (!event.preparation.userWantsSummary) return;
185
+ if (event.preparation.entriesToSummarize.length === 0) return;
186
+
187
+ const summary = await myCustomSummarizer(event.preparation.entriesToSummarize);
188
+ return { summary: { summary, details: { custom: true } } };
189
+ });
190
+ }
191
+ ```
192
+
193
+ ## Error Handling
194
+
195
+ - Summarization failure: cancels navigation, shows error
196
+ - User abort (Escape): cancels navigation
197
+ - Hook returns `cancel: true`: cancels navigation silently
package/docs/tui.md ADDED
@@ -0,0 +1,341 @@
1
+ > pi can create TUI components. Ask it to build one for your use case.
2
+
3
+ # TUI Components
4
+
5
+ Hooks and custom tools can render custom TUI components for interactive user interfaces. This page covers the component system and available building blocks.
6
+
7
+ **Source:** [`@oh-my-pi/pi-tui`](https://github.com/badlogic/pi-mono/tree/main/packages/tui)
8
+
9
+ ## Component Interface
10
+
11
+ All components implement:
12
+
13
+ ```typescript
14
+ interface Component {
15
+ render(width: number): string[];
16
+ handleInput?(data: string): void;
17
+ invalidate?(): void;
18
+ }
19
+ ```
20
+
21
+ | Method | Description |
22
+ | -------------------- | ------------------------------------------------------------------------------ |
23
+ | `render(width)` | Return array of strings (one per line). Each line **must not exceed `width`**. |
24
+ | `handleInput?(data)` | Receive keyboard input when component has focus. |
25
+ | `invalidate?()` | Clear cached render state. |
26
+
27
+ ## Using Components
28
+
29
+ **In hooks** via `ctx.ui.custom()`:
30
+
31
+ ```typescript
32
+ pi.on("session_start", async (_event, ctx) => {
33
+ const handle = ctx.ui.custom(myComponent);
34
+ // handle.requestRender() - trigger re-render
35
+ // handle.close() - restore normal UI
36
+ });
37
+ ```
38
+
39
+ **In custom tools** via `pi.ui.custom()`:
40
+
41
+ ```typescript
42
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
43
+ const handle = pi.ui.custom(myComponent);
44
+ // ...
45
+ handle.close();
46
+ }
47
+ ```
48
+
49
+ ## Built-in Components
50
+
51
+ Import from `@oh-my-pi/pi-tui`:
52
+
53
+ ```typescript
54
+ import { Text, Box, Container, Spacer, Markdown } from "@oh-my-pi/pi-tui";
55
+ ```
56
+
57
+ ### Text
58
+
59
+ Multi-line text with word wrapping.
60
+
61
+ ```typescript
62
+ const text = new Text(
63
+ "Hello World", // content
64
+ 1, // paddingX (default: 1)
65
+ 1, // paddingY (default: 1)
66
+ (s) => bgGray(s) // optional background function
67
+ );
68
+ text.setText("Updated");
69
+ ```
70
+
71
+ ### Box
72
+
73
+ Container with padding and background color.
74
+
75
+ ```typescript
76
+ const box = new Box(
77
+ 1, // paddingX
78
+ 1, // paddingY
79
+ (s) => bgGray(s) // background function
80
+ );
81
+ box.addChild(new Text("Content", 0, 0));
82
+ box.setBgFn((s) => bgBlue(s));
83
+ ```
84
+
85
+ ### Container
86
+
87
+ Groups child components vertically.
88
+
89
+ ```typescript
90
+ const container = new Container();
91
+ container.addChild(component1);
92
+ container.addChild(component2);
93
+ container.removeChild(component1);
94
+ ```
95
+
96
+ ### Spacer
97
+
98
+ Empty vertical space.
99
+
100
+ ```typescript
101
+ const spacer = new Spacer(2); // 2 empty lines
102
+ ```
103
+
104
+ ### Markdown
105
+
106
+ Renders markdown with syntax highlighting.
107
+
108
+ ```typescript
109
+ const md = new Markdown(
110
+ "# Title\n\nSome **bold** text",
111
+ 1, // paddingX
112
+ 1, // paddingY
113
+ theme // MarkdownTheme (see below)
114
+ );
115
+ md.setText("Updated markdown");
116
+ ```
117
+
118
+ ### Image
119
+
120
+ Renders images in supported terminals (Kitty, iTerm2, Ghostty, WezTerm).
121
+
122
+ ```typescript
123
+ const image = new Image(
124
+ base64Data, // base64-encoded image
125
+ "image/png", // MIME type
126
+ theme, // ImageTheme
127
+ { maxWidthCells: 80, maxHeightCells: 24 }
128
+ );
129
+ ```
130
+
131
+ ## Keyboard Input
132
+
133
+ Use key detection helpers:
134
+
135
+ ```typescript
136
+ import {
137
+ isEnter, isEscape, isTab,
138
+ isArrowUp, isArrowDown, isArrowLeft, isArrowRight,
139
+ isCtrlC, isCtrlO, isBackspace, isDelete,
140
+ // ... and more
141
+ } from "@oh-my-pi/pi-tui";
142
+
143
+ handleInput(data: string) {
144
+ if (isArrowUp(data)) {
145
+ this.selectedIndex--;
146
+ } else if (isEnter(data)) {
147
+ this.onSelect?.(this.selectedIndex);
148
+ } else if (isEscape(data)) {
149
+ this.onCancel?.();
150
+ }
151
+ }
152
+ ```
153
+
154
+ ## Line Width
155
+
156
+ **Critical:** Each line from `render()` must not exceed the `width` parameter.
157
+
158
+ ```typescript
159
+ import { visibleWidth, truncateToWidth } from "@oh-my-pi/pi-tui";
160
+
161
+ render(width: number): string[] {
162
+ // Truncate long lines
163
+ return [truncateToWidth(this.text, width)];
164
+ }
165
+ ```
166
+
167
+ Utilities:
168
+
169
+ - `visibleWidth(str)` - Get display width (ignores ANSI codes)
170
+ - `truncateToWidth(str, width, ellipsis?)` - Truncate with optional ellipsis
171
+ - `wrapTextWithAnsi(str, width)` - Word wrap preserving ANSI codes
172
+
173
+ ## Creating Custom Components
174
+
175
+ Example: Interactive selector
176
+
177
+ ```typescript
178
+ import { isEnter, isEscape, isArrowUp, isArrowDown, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
179
+
180
+ class MySelector {
181
+ private items: string[];
182
+ private selected = 0;
183
+ private cachedWidth?: number;
184
+ private cachedLines?: string[];
185
+
186
+ public onSelect?: (item: string) => void;
187
+ public onCancel?: () => void;
188
+
189
+ constructor(items: string[]) {
190
+ this.items = items;
191
+ }
192
+
193
+ handleInput(data: string): void {
194
+ if (isArrowUp(data) && this.selected > 0) {
195
+ this.selected--;
196
+ this.invalidate();
197
+ } else if (isArrowDown(data) && this.selected < this.items.length - 1) {
198
+ this.selected++;
199
+ this.invalidate();
200
+ } else if (isEnter(data)) {
201
+ this.onSelect?.(this.items[this.selected]);
202
+ } else if (isEscape(data)) {
203
+ this.onCancel?.();
204
+ }
205
+ }
206
+
207
+ render(width: number): string[] {
208
+ if (this.cachedLines && this.cachedWidth === width) {
209
+ return this.cachedLines;
210
+ }
211
+
212
+ this.cachedLines = this.items.map((item, i) => {
213
+ const prefix = i === this.selected ? "> " : " ";
214
+ return truncateToWidth(prefix + item, width);
215
+ });
216
+ this.cachedWidth = width;
217
+ return this.cachedLines;
218
+ }
219
+
220
+ invalidate(): void {
221
+ this.cachedWidth = undefined;
222
+ this.cachedLines = undefined;
223
+ }
224
+ }
225
+ ```
226
+
227
+ Usage in a hook:
228
+
229
+ ```typescript
230
+ pi.registerCommand("pick", {
231
+ description: "Pick an item",
232
+ handler: async (args, ctx) => {
233
+ const items = ["Option A", "Option B", "Option C"];
234
+ const selector = new MySelector(items);
235
+
236
+ let handle: { close: () => void; requestRender: () => void };
237
+
238
+ await new Promise<void>((resolve) => {
239
+ selector.onSelect = (item) => {
240
+ ctx.ui.notify(`Selected: ${item}`, "info");
241
+ handle.close();
242
+ resolve();
243
+ };
244
+ selector.onCancel = () => {
245
+ handle.close();
246
+ resolve();
247
+ };
248
+ handle = ctx.ui.custom(selector);
249
+ });
250
+ },
251
+ });
252
+ ```
253
+
254
+ ## Theming
255
+
256
+ Components accept theme objects for styling.
257
+
258
+ **In `renderCall`/`renderResult`**, use the `theme` parameter:
259
+
260
+ ```typescript
261
+ renderResult(result, options, theme) {
262
+ // Use theme.fg() for foreground colors
263
+ return new Text(theme.fg("success", "Done!"), 0, 0);
264
+
265
+ // Use theme.bg() for background colors
266
+ const styled = theme.bg("toolPendingBg", theme.fg("accent", "text"));
267
+ }
268
+ ```
269
+
270
+ **Foreground colors** (`theme.fg(color, text)`):
271
+
272
+ | Category | Colors |
273
+ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
274
+ | General | `text`, `accent`, `muted`, `dim` |
275
+ | Status | `success`, `error`, `warning` |
276
+ | Borders | `border`, `borderAccent`, `borderMuted` |
277
+ | Messages | `userMessageText`, `customMessageText`, `customMessageLabel` |
278
+ | Tools | `toolTitle`, `toolOutput` |
279
+ | Diffs | `toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext` |
280
+ | Markdown | `mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet` |
281
+ | Syntax | `syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation` |
282
+ | Thinking | `thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh` |
283
+ | Modes | `bashMode` |
284
+
285
+ **Background colors** (`theme.bg(color, text)`):
286
+
287
+ `selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`
288
+
289
+ **For Markdown**, use `getMarkdownTheme()`:
290
+
291
+ ```typescript
292
+ import { getMarkdownTheme } from "@oh-my-pi/pi-coding-agent";
293
+ import { Markdown } from "@oh-my-pi/pi-tui";
294
+
295
+ renderResult(result, options, theme) {
296
+ const mdTheme = getMarkdownTheme();
297
+ return new Markdown(result.details.markdown, 0, 0, mdTheme);
298
+ }
299
+ ```
300
+
301
+ **For custom components**, define your own theme interface:
302
+
303
+ ```typescript
304
+ interface MyTheme {
305
+ selected: (s: string) => string;
306
+ normal: (s: string) => string;
307
+ }
308
+ ```
309
+
310
+ ## Performance
311
+
312
+ Cache rendered output when possible:
313
+
314
+ ```typescript
315
+ class CachedComponent {
316
+ private cachedWidth?: number;
317
+ private cachedLines?: string[];
318
+
319
+ render(width: number): string[] {
320
+ if (this.cachedLines && this.cachedWidth === width) {
321
+ return this.cachedLines;
322
+ }
323
+ // ... compute lines ...
324
+ this.cachedWidth = width;
325
+ this.cachedLines = lines;
326
+ return lines;
327
+ }
328
+
329
+ invalidate(): void {
330
+ this.cachedWidth = undefined;
331
+ this.cachedLines = undefined;
332
+ }
333
+ }
334
+ ```
335
+
336
+ Call `invalidate()` when state changes, then `handle.requestRender()` to trigger re-render.
337
+
338
+ ## Examples
339
+
340
+ - **Snake game**: [examples/hooks/snake.ts](../examples/hooks/snake.ts) - Full game with keyboard input, game loop, state persistence
341
+ - **Custom tool rendering**: [examples/custom-tools/todo/](../examples/custom-tools/todo/) - Custom `renderCall` and `renderResult`
@@ -0,0 +1,21 @@
1
+ # Examples
2
+
3
+ Example code for pi-coding-agent SDK, hooks, and custom tools.
4
+
5
+ ## Directories
6
+
7
+ ### [sdk/](sdk/)
8
+ Programmatic usage via `createAgentSession()`. Shows how to customize models, prompts, tools, hooks, and session management.
9
+
10
+ ### [hooks/](hooks/)
11
+ Example hooks for intercepting tool calls, adding safety gates, and integrating with external systems.
12
+
13
+ ### [custom-tools/](custom-tools/)
14
+ Example custom tools that extend the agent's capabilities.
15
+
16
+ ## Documentation
17
+
18
+ - [SDK Reference](sdk/README.md)
19
+ - [Hooks Documentation](../docs/hooks.md)
20
+ - [Custom Tools Documentation](../docs/custom-tools.md)
21
+ - [Skills Documentation](../docs/skills.md)
@@ -0,0 +1,124 @@
1
+ # Custom Tools Examples
2
+
3
+ Example custom tools for pi-coding-agent.
4
+
5
+ ## Examples
6
+
7
+ Each example uses the `subdirectory/index.ts` structure required for tool discovery.
8
+
9
+ ### hello/
10
+
11
+ Minimal example showing the basic structure of a custom tool.
12
+
13
+ ### question/
14
+
15
+ Demonstrates `pi.ui.select()` for asking the user questions with options.
16
+
17
+ ### todo/
18
+
19
+ Full-featured example demonstrating:
20
+
21
+ - `onSession` for state reconstruction from session history
22
+ - Custom `renderCall` and `renderResult`
23
+ - Proper branching support via details storage
24
+ - State management without external files
25
+
26
+ ### subagent/
27
+
28
+ Delegate tasks to specialized subagents with isolated context windows. Includes:
29
+
30
+ - `index.ts` - The custom tool (single, parallel, and chain modes)
31
+ - `agents.ts` - Agent discovery helper
32
+ - `agents/` - Sample agent definitions (scout, planner, reviewer, worker)
33
+ - `commands/` - Workflow presets (/implement, /scout-and-plan, /implement-and-review)
34
+
35
+ See [subagent/README.md](subagent/README.md) for full documentation.
36
+
37
+ ## Usage
38
+
39
+ ```bash
40
+ # Test directly (can point to any .ts file)
41
+ pi --tool examples/custom-tools/todo/index.ts
42
+
43
+ # Or copy entire folder to tools directory for persistent use
44
+ cp -r todo ~/.pi/agent/tools/
45
+ ```
46
+
47
+ Then in pi:
48
+
49
+ ```
50
+ > add a todo "test custom tools"
51
+ > list todos
52
+ > toggle todo #1
53
+ > clear todos
54
+ ```
55
+
56
+ ## Writing Custom Tools
57
+
58
+ See [docs/custom-tools.md](../../docs/custom-tools.md) for full documentation.
59
+
60
+ ### Key Points
61
+
62
+ **Factory pattern:**
63
+
64
+ ```typescript
65
+ import { Type } from "@sinclair/typebox";
66
+ import { StringEnum } from "@oh-my-pi/pi-ai";
67
+ import { Text } from "@oh-my-pi/pi-tui";
68
+ import type { CustomToolFactory } from "@oh-my-pi/pi-coding-agent";
69
+
70
+ const factory: CustomToolFactory = (pi) => ({
71
+ name: "my_tool",
72
+ label: "My Tool",
73
+ description: "Tool description for LLM",
74
+ parameters: Type.Object({
75
+ action: StringEnum(["list", "add"] as const),
76
+ }),
77
+
78
+ // Called on session start/switch/branch/clear
79
+ onSession(event) {
80
+ // Reconstruct state from event.entries
81
+ },
82
+
83
+ async execute(toolCallId, params) {
84
+ return {
85
+ content: [{ type: "text", text: "Result" }],
86
+ details: {
87
+ /* for rendering and state reconstruction */
88
+ },
89
+ };
90
+ },
91
+ });
92
+
93
+ export default factory;
94
+ ```
95
+
96
+ **Custom rendering:**
97
+
98
+ ```typescript
99
+ renderCall(args, theme) {
100
+ return new Text(
101
+ theme.fg("toolTitle", theme.bold("my_tool ")) + args.action,
102
+ 0, 0 // No padding - Box handles it
103
+ );
104
+ },
105
+
106
+ renderResult(result, { expanded, isPartial }, theme) {
107
+ if (isPartial) {
108
+ return new Text(theme.fg("warning", "Working..."), 0, 0);
109
+ }
110
+ return new Text(theme.fg("success", "✓ Done"), 0, 0);
111
+ },
112
+ ```
113
+
114
+ **Use StringEnum for string parameters** (required for Google API compatibility):
115
+
116
+ ```typescript
117
+ import { StringEnum } from "@oh-my-pi/pi-ai";
118
+
119
+ // Good
120
+ action: StringEnum(["list", "add"] as const);
121
+
122
+ // Bad - doesn't work with Google
123
+ action: Type.Union([Type.Literal("list"), Type.Literal("add")]);
124
+ ```
@@ -0,0 +1,20 @@
1
+ import type { CustomToolFactory } from "@oh-my-pi/pi-coding-agent";
2
+
3
+ const factory: CustomToolFactory = (pi) => ({
4
+ name: "hello",
5
+ label: "Hello",
6
+ description: "A simple greeting tool",
7
+ parameters: pi.typebox.Type.Object({
8
+ name: pi.typebox.Type.String({ description: "Name to greet" }),
9
+ }),
10
+
11
+ async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
12
+ const { name } = params;
13
+ return {
14
+ content: [{ type: "text", text: `Hello, ${name}!` }],
15
+ details: { greeted: name },
16
+ };
17
+ },
18
+ });
19
+
20
+ export default factory;