@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.
- package/CHANGELOG.md +1228 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +637 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/reviewer.md +35 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +81 -0
- package/src/cli/args.ts +246 -0
- package/src/cli/file-processor.ts +72 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +650 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli.ts +10 -0
- package/src/commands/init.md +20 -0
- package/src/config.ts +159 -0
- package/src/core/agent-session.ts +1900 -0
- package/src/core/auth-storage.ts +236 -0
- package/src/core/bash-executor.ts +196 -0
- package/src/core/compaction/branch-summarization.ts +343 -0
- package/src/core/compaction/compaction.ts +742 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +154 -0
- package/src/core/custom-tools/index.ts +21 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +169 -0
- package/src/core/custom-tools/wrapper.ts +28 -0
- package/src/core/exec.ts +129 -0
- package/src/core/export-html/index.ts +211 -0
- package/src/core/export-html/template.css +781 -0
- package/src/core/export-html/template.html +54 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +312 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +99 -0
- package/src/core/hooks/types.ts +773 -0
- package/src/core/index.ts +52 -0
- package/src/core/mcp/client.ts +158 -0
- package/src/core/mcp/config.ts +154 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +68 -0
- package/src/core/mcp/manager.ts +181 -0
- package/src/core/mcp/tool-bridge.ts +148 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +220 -0
- package/src/core/messages.ts +189 -0
- package/src/core/model-registry.ts +317 -0
- package/src/core/model-resolver.ts +393 -0
- package/src/core/plugins/doctor.ts +59 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +338 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +32 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +760 -0
- package/src/core/session-manager.ts +1128 -0
- package/src/core/settings-manager.ts +443 -0
- package/src/core/skills.ts +437 -0
- package/src/core/slash-commands.ts +248 -0
- package/src/core/system-prompt.ts +439 -0
- package/src/core/timings.ts +25 -0
- package/src/core/tools/ask.ts +211 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +250 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +475 -0
- package/src/core/tools/edit.ts +208 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +64 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/logger.ts +56 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +196 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +337 -0
- package/src/core/tools/exa/types.ts +168 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +261 -0
- package/src/core/tools/grep.ts +555 -0
- package/src/core/tools/index.ts +202 -0
- package/src/core/tools/ls.ts +140 -0
- package/src/core/tools/lsp/client.ts +605 -0
- package/src/core/tools/lsp/config.ts +147 -0
- package/src/core/tools/lsp/edits.ts +101 -0
- package/src/core/tools/lsp/index.ts +804 -0
- package/src/core/tools/lsp/render.ts +447 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +463 -0
- package/src/core/tools/lsp/utils.ts +486 -0
- package/src/core/tools/notebook.ts +229 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +240 -0
- package/src/core/tools/renderers.ts +540 -0
- package/src/core/tools/task/agents.ts +153 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/bundled-agents/browser.md +71 -0
- package/src/core/tools/task/bundled-agents/explore.md +82 -0
- package/src/core/tools/task/bundled-agents/plan.md +54 -0
- package/src/core/tools/task/bundled-agents/reviewer.md +59 -0
- package/src/core/tools/task/bundled-agents/task.md +53 -0
- package/src/core/tools/task/bundled-commands/architect-plan.md +10 -0
- package/src/core/tools/task/bundled-commands/implement-with-critic.md +11 -0
- package/src/core/tools/task/bundled-commands/implement.md +11 -0
- package/src/core/tools/task/commands.ts +213 -0
- package/src/core/tools/task/discovery.ts +208 -0
- package/src/core/tools/task/executor.ts +367 -0
- package/src/core/tools/task/index.ts +388 -0
- package/src/core/tools/task/model-resolver.ts +115 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +232 -0
- package/src/core/tools/task/types.ts +99 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2370 -0
- package/src/core/tools/web-search/auth.ts +193 -0
- package/src/core/tools/web-search/index.ts +537 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +302 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +182 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +99 -0
- package/src/index.ts +176 -0
- package/src/main.ts +464 -0
- package/src/migrations.ts +135 -0
- package/src/modes/index.ts +43 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +196 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/footer.ts +381 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +247 -0
- package/src/modes/interactive/components/oauth-selector.ts +120 -0
- package/src/modes/interactive/components/plugin-settings.ts +479 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +204 -0
- package/src/modes/interactive/components/settings-selector.ts +453 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +675 -0
- package/src/modes/interactive/components/tree-selector.ts +866 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +183 -0
- package/src/modes/interactive/interactive-mode.ts +2516 -0
- package/src/modes/interactive/theme/dark.json +101 -0
- package/src/modes/interactive/theme/light.json +98 -0
- package/src/modes/interactive/theme/theme-schema.json +308 -0
- package/src/modes/interactive/theme/theme.ts +998 -0
- package/src/modes/print-mode.ts +128 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +483 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell.ts +276 -0
- 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;
|