@mariozechner/pi-coding-agent 0.30.2 → 0.31.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 +244 -1
- package/README.md +105 -84
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +5 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/file-processor.d.ts +3 -3
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +7 -10
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +73 -34
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +464 -210
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +2 -2
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +2 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts +2 -2
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +2 -2
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +84 -0
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -0
- package/dist/core/compaction/branch-summarization.js +233 -0
- package/dist/core/compaction/branch-summarization.js.map +1 -0
- package/dist/core/{compaction.d.ts → compaction/compaction.d.ts} +38 -19
- package/dist/core/compaction/compaction.d.ts.map +1 -0
- package/dist/core/compaction/compaction.js +558 -0
- package/dist/core/compaction/compaction.js.map +1 -0
- package/dist/core/compaction/index.d.ts +7 -0
- package/dist/core/compaction/index.d.ts.map +1 -0
- package/dist/core/compaction/index.js +7 -0
- package/dist/core/compaction/index.js.map +1 -0
- package/dist/core/compaction/utils.d.ts +35 -0
- package/dist/core/compaction/utils.d.ts.map +1 -0
- package/dist/core/compaction/utils.js +138 -0
- package/dist/core/compaction/utils.js.map +1 -0
- package/dist/core/custom-tools/index.d.ts +2 -1
- package/dist/core/custom-tools/index.d.ts.map +1 -1
- package/dist/core/custom-tools/index.js +1 -0
- package/dist/core/custom-tools/index.js.map +1 -1
- package/dist/core/custom-tools/loader.d.ts.map +1 -1
- package/dist/core/custom-tools/loader.js +13 -80
- package/dist/core/custom-tools/loader.js.map +1 -1
- package/dist/core/custom-tools/types.d.ts +84 -59
- package/dist/core/custom-tools/types.d.ts.map +1 -1
- package/dist/core/custom-tools/types.js.map +1 -1
- package/dist/core/custom-tools/wrapper.d.ts +15 -0
- package/dist/core/custom-tools/wrapper.d.ts.map +1 -0
- package/dist/core/custom-tools/wrapper.js +23 -0
- package/dist/core/custom-tools/wrapper.js.map +1 -0
- package/dist/core/exec.d.ts +29 -0
- package/dist/core/exec.d.ts.map +1 -0
- package/dist/core/exec.js +71 -0
- package/dist/core/exec.js.map +1 -0
- package/dist/core/export-html/index.d.ts +17 -0
- package/dist/core/export-html/index.d.ts.map +1 -0
- package/dist/core/export-html/index.js +171 -0
- package/dist/core/export-html/index.js.map +1 -0
- package/dist/core/export-html/template.css +781 -0
- package/dist/core/export-html/template.html +54 -0
- package/dist/core/export-html/template.js +1185 -0
- package/dist/core/export-html/vendor/highlight.min.js +1213 -0
- package/dist/core/export-html/vendor/marked.min.js +6 -0
- package/dist/core/hooks/index.d.ts +4 -4
- package/dist/core/hooks/index.d.ts.map +1 -1
- package/dist/core/hooks/index.js +3 -3
- package/dist/core/hooks/index.js.map +1 -1
- package/dist/core/hooks/loader.d.ts +40 -5
- package/dist/core/hooks/loader.d.ts.map +1 -1
- package/dist/core/hooks/loader.js +43 -10
- package/dist/core/hooks/loader.js.map +1 -1
- package/dist/core/hooks/runner.d.ts +94 -18
- package/dist/core/hooks/runner.d.ts.map +1 -1
- package/dist/core/hooks/runner.js +199 -120
- package/dist/core/hooks/runner.js.map +1 -1
- package/dist/core/hooks/tool-wrapper.d.ts +1 -1
- package/dist/core/hooks/tool-wrapper.d.ts.map +1 -1
- package/dist/core/hooks/tool-wrapper.js +36 -19
- package/dist/core/hooks/tool-wrapper.js.map +1 -1
- package/dist/core/hooks/types.d.ts +407 -96
- package/dist/core/hooks/types.d.ts.map +1 -1
- package/dist/core/hooks/types.js.map +1 -1
- package/dist/core/index.d.ts +4 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/messages.d.ts +44 -12
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +82 -34
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-registry.d.ts +5 -5
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +7 -7
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +7 -7
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +45 -14
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/sdk.d.ts +7 -10
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +88 -32
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +202 -36
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +565 -133
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +9 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +13 -12
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +6 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +33 -0
- package/dist/core/tools/edit-diff.d.ts.map +1 -0
- package/dist/core/tools/edit-diff.js +171 -0
- package/dist/core/tools/edit-diff.js.map +1 -0
- package/dist/core/tools/edit.d.ts +7 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +20 -95
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/write.d.ts +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +22 -21
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +3 -4
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +6 -2
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts +12 -0
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -0
- package/dist/modes/interactive/components/bordered-loader.js +30 -0
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -0
- package/dist/modes/interactive/components/branch-summary-message.d.ts +14 -0
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/branch-summary-message.js +35 -0
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -0
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +14 -0
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/compaction-summary-message.js +36 -0
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -0
- package/dist/modes/interactive/components/dynamic-border.d.ts +5 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js +5 -1
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +12 -6
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +57 -25
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/hook-editor.d.ts +15 -0
- package/dist/modes/interactive/components/hook-editor.d.ts.map +1 -0
- package/dist/modes/interactive/components/hook-editor.js +95 -0
- package/dist/modes/interactive/components/hook-editor.js.map +1 -0
- package/dist/modes/interactive/components/hook-message.d.ts +18 -0
- package/dist/modes/interactive/components/hook-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/hook-message.js +80 -0
- package/dist/modes/interactive/components/hook-message.js.map +1 -0
- package/dist/modes/interactive/components/model-selector.d.ts +3 -3
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +1 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +15 -2
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +70 -21
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +52 -0
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/tree-selector.js +745 -0
- package/dist/modes/interactive/components/tree-selector.js.map +1 -0
- package/dist/modes/interactive/components/user-message-selector.d.ts +3 -3
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js +1 -1
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +2 -5
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +29 -12
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +589 -208
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +13 -1
- package/dist/modes/interactive/theme/light.json +13 -1
- package/dist/modes/interactive/theme/theme-schema.json +34 -0
- package/dist/modes/interactive/theme/theme.d.ts +20 -2
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +135 -2
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts +3 -3
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +26 -20
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +13 -10
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +11 -10
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +88 -35
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +30 -11
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/shell.d.ts +4 -2
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +36 -7
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +2 -2
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/compaction.md +388 -0
- package/docs/custom-tools.md +146 -43
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +562 -596
- package/docs/rpc.md +33 -19
- package/docs/sdk.md +93 -21
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +172 -21
- package/docs/skills.md +2 -0
- package/docs/theme.md +31 -2
- package/docs/tree.md +197 -0
- package/docs/tui.md +343 -0
- package/examples/README.md +1 -9
- package/examples/custom-tools/hello/index.ts +4 -3
- package/examples/custom-tools/question/index.ts +4 -4
- package/examples/custom-tools/subagent/index.ts +7 -6
- package/examples/custom-tools/todo/index.ts +11 -5
- package/examples/hooks/README.md +29 -71
- package/examples/hooks/auto-commit-on-exit.ts +8 -9
- package/examples/hooks/confirm-destructive.ts +29 -30
- package/examples/hooks/custom-compaction.ts +20 -21
- package/examples/hooks/dirty-repo-guard.ts +41 -40
- package/examples/hooks/file-trigger.ts +10 -5
- package/examples/hooks/git-checkpoint.ts +16 -12
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +1 -1
- package/examples/hooks/protected-paths.ts +1 -1
- 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 +1 -1
- package/examples/sdk/02-custom-model.ts +1 -1
- package/examples/sdk/03-custom-prompt.ts +1 -1
- package/examples/sdk/04-skills.ts +1 -1
- package/examples/sdk/05-tools.ts +4 -4
- package/examples/sdk/06-hooks.ts +1 -1
- package/examples/sdk/07-context-files.ts +1 -1
- package/examples/sdk/08-slash-commands.ts +6 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +1 -1
- package/examples/sdk/10-settings.ts +1 -1
- package/examples/sdk/11-sessions.ts +1 -1
- package/examples/sdk/12-full-control.ts +4 -7
- package/package.json +6 -6
- package/dist/core/compaction.d.ts.map +0 -1
- package/dist/core/compaction.js +0 -412
- package/dist/core/compaction.js.map +0 -1
- package/dist/core/export-html.d.ts +0 -23
- package/dist/core/export-html.d.ts.map +0 -1
- package/dist/core/export-html.js +0 -1185
- package/dist/core/export-html.js.map +0 -1
- package/dist/modes/interactive/components/compaction.d.ts +0 -15
- package/dist/modes/interactive/components/compaction.d.ts.map +0 -1
- package/dist/modes/interactive/components/compaction.js +0 -41
- package/dist/modes/interactive/components/compaction.js.map +0 -1
- package/docs/hooks-v2.md +0 -385
- package/docs/session-tree.md +0 -452
package/docs/tui.md
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
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:** [`@mariozechner/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 `@mariozechner/pi-tui`:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { Text, Box, Container, Spacer, Markdown } from "@mariozechner/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 "@mariozechner/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 "@mariozechner/pi-tui";
|
|
160
|
+
|
|
161
|
+
render(width: number): string[] {
|
|
162
|
+
// Truncate long lines
|
|
163
|
+
return [truncateToWidth(this.text, width)];
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Utilities:
|
|
168
|
+
- `visibleWidth(str)` - Get display width (ignores ANSI codes)
|
|
169
|
+
- `truncateToWidth(str, width, ellipsis?)` - Truncate with optional ellipsis
|
|
170
|
+
- `wrapTextWithAnsi(str, width)` - Word wrap preserving ANSI codes
|
|
171
|
+
|
|
172
|
+
## Creating Custom Components
|
|
173
|
+
|
|
174
|
+
Example: Interactive selector
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import {
|
|
178
|
+
isEnter, isEscape, isArrowUp, isArrowDown,
|
|
179
|
+
truncateToWidth, visibleWidth
|
|
180
|
+
} from "@mariozechner/pi-tui";
|
|
181
|
+
|
|
182
|
+
class MySelector {
|
|
183
|
+
private items: string[];
|
|
184
|
+
private selected = 0;
|
|
185
|
+
private cachedWidth?: number;
|
|
186
|
+
private cachedLines?: string[];
|
|
187
|
+
|
|
188
|
+
public onSelect?: (item: string) => void;
|
|
189
|
+
public onCancel?: () => void;
|
|
190
|
+
|
|
191
|
+
constructor(items: string[]) {
|
|
192
|
+
this.items = items;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
handleInput(data: string): void {
|
|
196
|
+
if (isArrowUp(data) && this.selected > 0) {
|
|
197
|
+
this.selected--;
|
|
198
|
+
this.invalidate();
|
|
199
|
+
} else if (isArrowDown(data) && this.selected < this.items.length - 1) {
|
|
200
|
+
this.selected++;
|
|
201
|
+
this.invalidate();
|
|
202
|
+
} else if (isEnter(data)) {
|
|
203
|
+
this.onSelect?.(this.items[this.selected]);
|
|
204
|
+
} else if (isEscape(data)) {
|
|
205
|
+
this.onCancel?.();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
render(width: number): string[] {
|
|
210
|
+
if (this.cachedLines && this.cachedWidth === width) {
|
|
211
|
+
return this.cachedLines;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
this.cachedLines = this.items.map((item, i) => {
|
|
215
|
+
const prefix = i === this.selected ? "> " : " ";
|
|
216
|
+
return truncateToWidth(prefix + item, width);
|
|
217
|
+
});
|
|
218
|
+
this.cachedWidth = width;
|
|
219
|
+
return this.cachedLines;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
invalidate(): void {
|
|
223
|
+
this.cachedWidth = undefined;
|
|
224
|
+
this.cachedLines = undefined;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Usage in a hook:
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
pi.registerCommand("pick", {
|
|
233
|
+
description: "Pick an item",
|
|
234
|
+
handler: async (args, ctx) => {
|
|
235
|
+
const items = ["Option A", "Option B", "Option C"];
|
|
236
|
+
const selector = new MySelector(items);
|
|
237
|
+
|
|
238
|
+
let handle: { close: () => void; requestRender: () => void };
|
|
239
|
+
|
|
240
|
+
await new Promise<void>((resolve) => {
|
|
241
|
+
selector.onSelect = (item) => {
|
|
242
|
+
ctx.ui.notify(`Selected: ${item}`, "info");
|
|
243
|
+
handle.close();
|
|
244
|
+
resolve();
|
|
245
|
+
};
|
|
246
|
+
selector.onCancel = () => {
|
|
247
|
+
handle.close();
|
|
248
|
+
resolve();
|
|
249
|
+
};
|
|
250
|
+
handle = ctx.ui.custom(selector);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Theming
|
|
257
|
+
|
|
258
|
+
Components accept theme objects for styling.
|
|
259
|
+
|
|
260
|
+
**In `renderCall`/`renderResult`**, use the `theme` parameter:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
renderResult(result, options, theme) {
|
|
264
|
+
// Use theme.fg() for foreground colors
|
|
265
|
+
return new Text(theme.fg("success", "Done!"), 0, 0);
|
|
266
|
+
|
|
267
|
+
// Use theme.bg() for background colors
|
|
268
|
+
const styled = theme.bg("toolPendingBg", theme.fg("accent", "text"));
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Foreground colors** (`theme.fg(color, text)`):
|
|
273
|
+
|
|
274
|
+
| Category | Colors |
|
|
275
|
+
|----------|--------|
|
|
276
|
+
| General | `text`, `accent`, `muted`, `dim` |
|
|
277
|
+
| Status | `success`, `error`, `warning` |
|
|
278
|
+
| Borders | `border`, `borderAccent`, `borderMuted` |
|
|
279
|
+
| Messages | `userMessageText`, `customMessageText`, `customMessageLabel` |
|
|
280
|
+
| Tools | `toolTitle`, `toolOutput` |
|
|
281
|
+
| Diffs | `toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext` |
|
|
282
|
+
| Markdown | `mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet` |
|
|
283
|
+
| Syntax | `syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation` |
|
|
284
|
+
| Thinking | `thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh` |
|
|
285
|
+
| Modes | `bashMode` |
|
|
286
|
+
|
|
287
|
+
**Background colors** (`theme.bg(color, text)`):
|
|
288
|
+
|
|
289
|
+
`selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`
|
|
290
|
+
|
|
291
|
+
**For Markdown**, use `getMarkdownTheme()`:
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import { getMarkdownTheme } from "@mariozechner/pi-coding-agent";
|
|
295
|
+
import { Markdown } from "@mariozechner/pi-tui";
|
|
296
|
+
|
|
297
|
+
renderResult(result, options, theme) {
|
|
298
|
+
const mdTheme = getMarkdownTheme();
|
|
299
|
+
return new Markdown(result.details.markdown, 0, 0, mdTheme);
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**For custom components**, define your own theme interface:
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
interface MyTheme {
|
|
307
|
+
selected: (s: string) => string;
|
|
308
|
+
normal: (s: string) => string;
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Performance
|
|
313
|
+
|
|
314
|
+
Cache rendered output when possible:
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
class CachedComponent {
|
|
318
|
+
private cachedWidth?: number;
|
|
319
|
+
private cachedLines?: string[];
|
|
320
|
+
|
|
321
|
+
render(width: number): string[] {
|
|
322
|
+
if (this.cachedLines && this.cachedWidth === width) {
|
|
323
|
+
return this.cachedLines;
|
|
324
|
+
}
|
|
325
|
+
// ... compute lines ...
|
|
326
|
+
this.cachedWidth = width;
|
|
327
|
+
this.cachedLines = lines;
|
|
328
|
+
return lines;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
invalidate(): void {
|
|
332
|
+
this.cachedWidth = undefined;
|
|
333
|
+
this.cachedLines = undefined;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Call `invalidate()` when state changes, then `handle.requestRender()` to trigger re-render.
|
|
339
|
+
|
|
340
|
+
## Examples
|
|
341
|
+
|
|
342
|
+
- **Snake game**: [examples/hooks/snake.ts](../examples/hooks/snake.ts) - Full game with keyboard input, game loop, state persistence
|
|
343
|
+
- **Custom tool rendering**: [examples/custom-tools/todo/](../examples/custom-tools/todo/) - Custom `renderCall` and `renderResult`
|
package/examples/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Examples
|
|
2
2
|
|
|
3
|
-
Example code for pi-coding-agent.
|
|
3
|
+
Example code for pi-coding-agent SDK, hooks, and custom tools.
|
|
4
4
|
|
|
5
5
|
## Directories
|
|
6
6
|
|
|
@@ -13,14 +13,6 @@ Example hooks for intercepting tool calls, adding safety gates, and integrating
|
|
|
13
13
|
### [custom-tools/](custom-tools/)
|
|
14
14
|
Example custom tools that extend the agent's capabilities.
|
|
15
15
|
|
|
16
|
-
## Running Examples
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
cd packages/coding-agent
|
|
20
|
-
npx tsx examples/sdk/01-minimal.ts
|
|
21
|
-
npx tsx examples/hooks/permission-gate.ts
|
|
22
|
-
```
|
|
23
|
-
|
|
24
16
|
## Documentation
|
|
25
17
|
|
|
26
18
|
- [SDK Reference](sdk/README.md)
|
|
@@ -9,10 +9,11 @@ const factory: CustomToolFactory = (_pi) => ({
|
|
|
9
9
|
name: Type.String({ description: "Name to greet" }),
|
|
10
10
|
}),
|
|
11
11
|
|
|
12
|
-
async execute(_toolCallId, params) {
|
|
12
|
+
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
13
|
+
const { name } = params as { name: string };
|
|
13
14
|
return {
|
|
14
|
-
content: [{ type: "text", text: `Hello, ${
|
|
15
|
-
details: { greeted:
|
|
15
|
+
content: [{ type: "text", text: `Hello, ${name}!` }],
|
|
16
|
+
details: { greeted: name },
|
|
16
17
|
};
|
|
17
18
|
},
|
|
18
19
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Question Tool - Let the LLM ask the user a question with options
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type { CustomTool, CustomToolFactory } from "@mariozechner/pi-coding-agent";
|
|
6
6
|
import { Text } from "@mariozechner/pi-tui";
|
|
7
7
|
import { Type } from "@sinclair/typebox";
|
|
8
8
|
|
|
@@ -18,13 +18,13 @@ const QuestionParams = Type.Object({
|
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
const factory: CustomToolFactory = (pi) => {
|
|
21
|
-
const tool:
|
|
21
|
+
const tool: CustomTool<typeof QuestionParams, QuestionDetails> = {
|
|
22
22
|
name: "question",
|
|
23
23
|
label: "Question",
|
|
24
24
|
description: "Ask the user a question and let them pick from options. Use when you need user input to proceed.",
|
|
25
25
|
parameters: QuestionParams,
|
|
26
26
|
|
|
27
|
-
async execute(_toolCallId, params) {
|
|
27
|
+
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
28
28
|
if (!pi.hasUI) {
|
|
29
29
|
return {
|
|
30
30
|
content: [{ type: "text", text: "Error: UI not available (running in non-interactive mode)" }],
|
|
@@ -41,7 +41,7 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
41
41
|
|
|
42
42
|
const answer = await pi.ui.select(params.question, params.options);
|
|
43
43
|
|
|
44
|
-
if (answer ===
|
|
44
|
+
if (answer === undefined) {
|
|
45
45
|
return {
|
|
46
46
|
content: [{ type: "text", text: "User cancelled the selection" }],
|
|
47
47
|
details: { question: params.question, options: params.options, answer: null },
|
|
@@ -16,13 +16,14 @@ import { spawn } from "node:child_process";
|
|
|
16
16
|
import * as fs from "node:fs";
|
|
17
17
|
import * as os from "node:os";
|
|
18
18
|
import * as path from "node:path";
|
|
19
|
-
import type { AgentToolResult
|
|
19
|
+
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
20
|
+
import type { Message } from "@mariozechner/pi-ai";
|
|
20
21
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
21
22
|
import {
|
|
22
|
-
type
|
|
23
|
+
type CustomTool,
|
|
24
|
+
type CustomToolAPI,
|
|
23
25
|
type CustomToolFactory,
|
|
24
26
|
getMarkdownTheme,
|
|
25
|
-
type ToolAPI,
|
|
26
27
|
} from "@mariozechner/pi-coding-agent";
|
|
27
28
|
import { Container, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
|
|
28
29
|
import { Type } from "@sinclair/typebox";
|
|
@@ -223,7 +224,7 @@ function writePromptToTempFile(agentName: string, prompt: string): { dir: string
|
|
|
223
224
|
type OnUpdateCallback = (partial: AgentToolResult<SubagentDetails>) => void;
|
|
224
225
|
|
|
225
226
|
async function runSingleAgent(
|
|
226
|
-
pi:
|
|
227
|
+
pi: CustomToolAPI,
|
|
227
228
|
agents: AgentConfig[],
|
|
228
229
|
agentName: string,
|
|
229
230
|
task: string,
|
|
@@ -410,7 +411,7 @@ const SubagentParams = Type.Object({
|
|
|
410
411
|
});
|
|
411
412
|
|
|
412
413
|
const factory: CustomToolFactory = (pi) => {
|
|
413
|
-
const tool:
|
|
414
|
+
const tool: CustomTool<typeof SubagentParams, SubagentDetails> = {
|
|
414
415
|
name: "subagent",
|
|
415
416
|
label: "Subagent",
|
|
416
417
|
get description() {
|
|
@@ -432,7 +433,7 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
432
433
|
},
|
|
433
434
|
parameters: SubagentParams,
|
|
434
435
|
|
|
435
|
-
async execute(_toolCallId, params,
|
|
436
|
+
async execute(_toolCallId, params, onUpdate, _ctx, signal) {
|
|
436
437
|
const agentScope: AgentScope = params.agentScope ?? "user";
|
|
437
438
|
const discovery = discoverAgents(pi.cwd, agentScope);
|
|
438
439
|
const agents = discovery.agents;
|
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
CustomTool,
|
|
14
|
+
CustomToolContext,
|
|
15
|
+
CustomToolFactory,
|
|
16
|
+
CustomToolSessionEvent,
|
|
17
|
+
} from "@mariozechner/pi-coding-agent";
|
|
13
18
|
import { Text } from "@mariozechner/pi-tui";
|
|
14
19
|
import { Type } from "@sinclair/typebox";
|
|
15
20
|
|
|
@@ -43,11 +48,12 @@ const factory: CustomToolFactory = (_pi) => {
|
|
|
43
48
|
* Reconstruct state from session entries.
|
|
44
49
|
* Scans tool results for this tool and applies them in order.
|
|
45
50
|
*/
|
|
46
|
-
const reconstructState = (
|
|
51
|
+
const reconstructState = (_event: CustomToolSessionEvent, ctx: CustomToolContext) => {
|
|
47
52
|
todos = [];
|
|
48
53
|
nextId = 1;
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
// Use getBranch() to get entries on the current branch
|
|
56
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
51
57
|
if (entry.type !== "message") continue;
|
|
52
58
|
const msg = entry.message;
|
|
53
59
|
|
|
@@ -63,7 +69,7 @@ const factory: CustomToolFactory = (_pi) => {
|
|
|
63
69
|
}
|
|
64
70
|
};
|
|
65
71
|
|
|
66
|
-
const tool:
|
|
72
|
+
const tool: CustomTool<typeof TodoParams, TodoDetails> = {
|
|
67
73
|
name: "todo",
|
|
68
74
|
label: "Todo",
|
|
69
75
|
description: "Manage a todo list. Actions: list, add (text), toggle (id), clear",
|
|
@@ -72,7 +78,7 @@ const factory: CustomToolFactory = (_pi) => {
|
|
|
72
78
|
// Called on session start/switch/branch/clear
|
|
73
79
|
onSession: reconstructState,
|
|
74
80
|
|
|
75
|
-
async execute(_toolCallId, params) {
|
|
81
|
+
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
76
82
|
switch (params.action) {
|
|
77
83
|
case "list":
|
|
78
84
|
return {
|
package/examples/hooks/README.md
CHANGED
|
@@ -2,97 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
Example hooks for pi-coding-agent.
|
|
4
4
|
|
|
5
|
-
## Examples
|
|
6
|
-
|
|
7
|
-
### permission-gate.ts
|
|
8
|
-
Prompts for confirmation before running dangerous bash commands (rm -rf, sudo, chmod 777, etc.).
|
|
9
|
-
|
|
10
|
-
### git-checkpoint.ts
|
|
11
|
-
Creates git stash checkpoints at each turn, allowing code restoration when branching.
|
|
12
|
-
|
|
13
|
-
### protected-paths.ts
|
|
14
|
-
Blocks writes to protected paths (.env, .git/, node_modules/).
|
|
15
|
-
|
|
16
|
-
### file-trigger.ts
|
|
17
|
-
Watches a trigger file and injects its contents into the conversation. Useful for external systems (CI, file watchers, webhooks) to send messages to the agent.
|
|
18
|
-
|
|
19
|
-
### confirm-destructive.ts
|
|
20
|
-
Prompts for confirmation before destructive session actions (clear, switch, branch). Demonstrates how to cancel `before_*` session events.
|
|
21
|
-
|
|
22
|
-
### dirty-repo-guard.ts
|
|
23
|
-
Prevents session changes when there are uncommitted git changes. Blocks clear/switch/branch until you commit.
|
|
24
|
-
|
|
25
|
-
### auto-commit-on-exit.ts
|
|
26
|
-
Automatically commits changes when the agent exits (shutdown event). Uses the last assistant message to generate a commit message.
|
|
27
|
-
|
|
28
|
-
### custom-compaction.ts
|
|
29
|
-
Custom context compaction that summarizes the entire conversation instead of keeping recent turns. Uses the `before_compact` hook event to intercept compaction and generate a comprehensive summary using `complete()` from the AI package. Useful when you want maximum context window space at the cost of losing exact conversation history.
|
|
30
|
-
|
|
31
5
|
## Usage
|
|
32
6
|
|
|
33
7
|
```bash
|
|
34
|
-
#
|
|
8
|
+
# Load a hook with --hook flag
|
|
35
9
|
pi --hook examples/hooks/permission-gate.ts
|
|
36
10
|
|
|
37
|
-
# Or copy to hooks directory for
|
|
11
|
+
# Or copy to hooks directory for auto-discovery
|
|
38
12
|
cp permission-gate.ts ~/.pi/agent/hooks/
|
|
39
13
|
```
|
|
40
14
|
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
| Hook | Description |
|
|
18
|
+
|------|-------------|
|
|
19
|
+
| `permission-gate.ts` | Prompts for confirmation before dangerous bash commands (rm -rf, sudo, etc.) |
|
|
20
|
+
| `git-checkpoint.ts` | Creates git stash checkpoints at each turn for code restoration on branch |
|
|
21
|
+
| `protected-paths.ts` | Blocks writes to protected paths (.env, .git/, node_modules/) |
|
|
22
|
+
| `file-trigger.ts` | Watches a trigger file and injects contents into conversation |
|
|
23
|
+
| `confirm-destructive.ts` | Confirms before destructive session actions (clear, switch, branch) |
|
|
24
|
+
| `dirty-repo-guard.ts` | Prevents session changes with uncommitted git changes |
|
|
25
|
+
| `auto-commit-on-exit.ts` | Auto-commits on exit using last assistant message for commit message |
|
|
26
|
+
| `custom-compaction.ts` | Custom compaction that summarizes entire conversation |
|
|
27
|
+
| `qna.ts` | Extracts questions from last response into editor via `ctx.ui.setEditorText()` |
|
|
28
|
+
| `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
|
|
29
|
+
| `status-line.ts` | Shows turn progress in footer via `ctx.ui.setStatus()` with themed colors |
|
|
30
|
+
| `handoff.ts` | Transfer context to a new focused session via `/handoff <goal>` |
|
|
31
|
+
|
|
41
32
|
## Writing Hooks
|
|
42
33
|
|
|
43
34
|
See [docs/hooks.md](../../docs/hooks.md) for full documentation.
|
|
44
35
|
|
|
45
|
-
### Key Points
|
|
46
|
-
|
|
47
|
-
**Hook structure:**
|
|
48
36
|
```typescript
|
|
49
37
|
import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
|
|
50
38
|
|
|
51
39
|
export default function (pi: HookAPI) {
|
|
52
|
-
|
|
53
|
-
// event.reason: "start" | "before_switch" | "switch" | "before_clear" | "clear" |
|
|
54
|
-
// "before_branch" | "branch" | "shutdown"
|
|
55
|
-
// event.targetTurnIndex: number (only for before_branch/branch)
|
|
56
|
-
// ctx.ui, ctx.exec, ctx.cwd, ctx.sessionFile, ctx.hasUI
|
|
57
|
-
|
|
58
|
-
// Cancel before_* actions:
|
|
59
|
-
if (event.reason === "before_clear") {
|
|
60
|
-
return { cancel: true };
|
|
61
|
-
}
|
|
62
|
-
return undefined;
|
|
63
|
-
});
|
|
64
|
-
|
|
40
|
+
// Subscribe to events
|
|
65
41
|
pi.on("tool_call", async (event, ctx) => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return { block: true, reason: "Blocked" };
|
|
42
|
+
if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
|
|
43
|
+
const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
|
|
44
|
+
if (!ok) return { block: true, reason: "Blocked by user" };
|
|
69
45
|
}
|
|
70
|
-
return undefined;
|
|
71
46
|
});
|
|
72
47
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
48
|
+
// Register custom commands
|
|
49
|
+
pi.registerCommand("hello", {
|
|
50
|
+
description: "Say hello",
|
|
51
|
+
handler: async (args, ctx) => {
|
|
52
|
+
ctx.ui.notify("Hello!", "info");
|
|
53
|
+
},
|
|
76
54
|
});
|
|
77
55
|
}
|
|
78
56
|
```
|
|
79
|
-
|
|
80
|
-
**Available events:**
|
|
81
|
-
- `session` - lifecycle events with before/after variants (can cancel before_* actions)
|
|
82
|
-
- `agent_start` / `agent_end` - per user prompt
|
|
83
|
-
- `turn_start` / `turn_end` - per LLM turn
|
|
84
|
-
- `tool_call` - before tool execution (can block)
|
|
85
|
-
- `tool_result` - after tool execution (can modify)
|
|
86
|
-
|
|
87
|
-
**UI methods:**
|
|
88
|
-
```typescript
|
|
89
|
-
const choice = await ctx.ui.select("Title", ["Option A", "Option B"]);
|
|
90
|
-
const confirmed = await ctx.ui.confirm("Title", "Are you sure?");
|
|
91
|
-
const input = await ctx.ui.input("Title", "placeholder");
|
|
92
|
-
ctx.ui.notify("Message", "info"); // or "warning", "error"
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
**Sending messages:**
|
|
96
|
-
```typescript
|
|
97
|
-
pi.send("Message to inject into conversation");
|
|
98
|
-
```
|
|
@@ -5,14 +5,12 @@
|
|
|
5
5
|
* Uses the last assistant message to generate a commit message.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { HookAPI } from "@mariozechner/pi-coding-agent
|
|
8
|
+
import type { HookAPI } from "@mariozechner/pi-coding-agent";
|
|
9
9
|
|
|
10
10
|
export default function (pi: HookAPI) {
|
|
11
|
-
pi.on("
|
|
12
|
-
if (event.reason !== "shutdown") return;
|
|
13
|
-
|
|
11
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
14
12
|
// Check for uncommitted changes
|
|
15
|
-
const { stdout: status, code } = await
|
|
13
|
+
const { stdout: status, code } = await pi.exec("git", ["status", "--porcelain"]);
|
|
16
14
|
|
|
17
15
|
if (code !== 0 || status.trim().length === 0) {
|
|
18
16
|
// Not a git repo or no changes
|
|
@@ -20,9 +18,10 @@ export default function (pi: HookAPI) {
|
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
// Find the last assistant message for commit context
|
|
21
|
+
const entries = ctx.sessionManager.getEntries();
|
|
23
22
|
let lastAssistantText = "";
|
|
24
|
-
for (let i =
|
|
25
|
-
const entry =
|
|
23
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
24
|
+
const entry = entries[i];
|
|
26
25
|
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
27
26
|
const content = entry.message.content;
|
|
28
27
|
if (Array.isArray(content)) {
|
|
@@ -40,8 +39,8 @@ export default function (pi: HookAPI) {
|
|
|
40
39
|
const commitMessage = `[pi] ${firstLine.slice(0, 50)}${firstLine.length > 50 ? "..." : ""}`;
|
|
41
40
|
|
|
42
41
|
// Stage and commit
|
|
43
|
-
await
|
|
44
|
-
const { code: commitCode } = await
|
|
42
|
+
await pi.exec("git", ["add", "-A"]);
|
|
43
|
+
const { code: commitCode } = await pi.exec("git", ["commit", "-m", commitMessage]);
|
|
45
44
|
|
|
46
45
|
if (commitCode === 0 && ctx.hasUI) {
|
|
47
46
|
ctx.ui.notify(`Auto-committed: ${commitMessage}`, "info");
|