@mariozechner/pi-coding-agent 0.50.9 → 0.51.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 +54 -0
- package/README.md +4 -3
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js +3 -1
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -1
- package/dist/core/extensions/index.d.ts +2 -2
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/types.d.ts +67 -5
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +4 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/extensions/wrapper.d.ts.map +1 -1
- package/dist/core/extensions/wrapper.js +1 -1
- package/dist/core/extensions/wrapper.js.map +1 -1
- package/dist/core/keybindings.d.ts +1 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +2 -0
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +19 -17
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +11 -9
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +1 -0
- package/dist/core/skills.js.map +1 -1
- package/dist/core/tools/bash.d.ts +11 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +18 -3
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts +2 -0
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts +2 -0
- 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 +2 -0
- 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 +7 -7
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +5 -5
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +2 -0
- 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 +2 -0
- 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 +2 -0
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts +3 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.js +13 -4
- package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +11 -2
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +58 -12
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +6 -0
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +43 -16
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +6 -15
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +6 -3
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts +7 -0
- package/dist/utils/clipboard-native.d.ts.map +1 -0
- package/dist/utils/clipboard-native.js +14 -0
- package/dist/utils/clipboard-native.js.map +1 -0
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +10 -1
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +14 -0
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/extensions.md +57 -9
- package/docs/keybindings.md +1 -0
- package/docs/models.md +43 -14
- package/docs/rpc.md +188 -1
- package/docs/termux.md +127 -0
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/antigravity-image-gen.ts +1 -1
- package/examples/extensions/bash-spawn-hook.ts +30 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/hello.ts +1 -1
- package/examples/extensions/question.ts +1 -1
- package/examples/extensions/questionnaire.ts +1 -1
- package/examples/extensions/rpc-demo.ts +124 -0
- package/examples/extensions/sandbox/index.ts +1 -1
- package/examples/extensions/shutdown-command.ts +2 -2
- package/examples/extensions/ssh.ts +4 -4
- package/examples/extensions/subagent/index.ts +1 -1
- package/examples/extensions/todo.ts +1 -1
- package/examples/extensions/tool-override.ts +1 -1
- package/examples/extensions/truncated-tool.ts +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/rpc-extension-ui.ts +632 -0
- package/examples/sdk/06-extensions.ts +1 -1
- package/package.json +7 -5
package/docs/rpc.md
CHANGED
|
@@ -903,6 +903,191 @@ Emitted when an extension throws an error.
|
|
|
903
903
|
}
|
|
904
904
|
```
|
|
905
905
|
|
|
906
|
+
## Extension UI Protocol
|
|
907
|
+
|
|
908
|
+
Extensions can request user interaction via `ctx.ui.select()`, `ctx.ui.confirm()`, etc. In RPC mode, these are translated into a request/response sub-protocol on top of the base command/event flow.
|
|
909
|
+
|
|
910
|
+
There are two categories of extension UI methods:
|
|
911
|
+
|
|
912
|
+
- **Dialog methods** (`select`, `confirm`, `input`, `editor`): emit an `extension_ui_request` on stdout and block until the client sends back an `extension_ui_response` on stdin with the matching `id`.
|
|
913
|
+
- **Fire-and-forget methods** (`notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`): emit an `extension_ui_request` on stdout but do not expect a response. The client can display the information or ignore it.
|
|
914
|
+
|
|
915
|
+
If a dialog method includes a `timeout` field, the agent-side will auto-resolve with a default value when the timeout expires. The client does not need to track timeouts.
|
|
916
|
+
|
|
917
|
+
Some `ExtensionUIContext` methods are not supported in RPC mode because they require direct TUI access:
|
|
918
|
+
- `custom()` returns `undefined`
|
|
919
|
+
- `setWorkingMessage()`, `setFooter()`, `setHeader()`, `setEditorComponent()` are no-ops
|
|
920
|
+
- `getEditorText()` returns `""`
|
|
921
|
+
|
|
922
|
+
### Extension UI Requests (stdout)
|
|
923
|
+
|
|
924
|
+
All requests have `type: "extension_ui_request"`, a unique `id`, and a `method` field.
|
|
925
|
+
|
|
926
|
+
#### select
|
|
927
|
+
|
|
928
|
+
Prompt the user to choose from a list. Dialog methods with a `timeout` field include the timeout in milliseconds; the agent auto-resolves with `undefined` if the client doesn't respond in time.
|
|
929
|
+
|
|
930
|
+
```json
|
|
931
|
+
{
|
|
932
|
+
"type": "extension_ui_request",
|
|
933
|
+
"id": "uuid-1",
|
|
934
|
+
"method": "select",
|
|
935
|
+
"title": "Allow dangerous command?",
|
|
936
|
+
"options": ["Allow", "Block"],
|
|
937
|
+
"timeout": 10000
|
|
938
|
+
}
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
Expected response: `extension_ui_response` with `value` (the selected option string) or `cancelled: true`.
|
|
942
|
+
|
|
943
|
+
#### confirm
|
|
944
|
+
|
|
945
|
+
Prompt the user for yes/no confirmation.
|
|
946
|
+
|
|
947
|
+
```json
|
|
948
|
+
{
|
|
949
|
+
"type": "extension_ui_request",
|
|
950
|
+
"id": "uuid-2",
|
|
951
|
+
"method": "confirm",
|
|
952
|
+
"title": "Clear session?",
|
|
953
|
+
"message": "All messages will be lost.",
|
|
954
|
+
"timeout": 5000
|
|
955
|
+
}
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
Expected response: `extension_ui_response` with `confirmed: true/false` or `cancelled: true`.
|
|
959
|
+
|
|
960
|
+
#### input
|
|
961
|
+
|
|
962
|
+
Prompt the user for free-form text.
|
|
963
|
+
|
|
964
|
+
```json
|
|
965
|
+
{
|
|
966
|
+
"type": "extension_ui_request",
|
|
967
|
+
"id": "uuid-3",
|
|
968
|
+
"method": "input",
|
|
969
|
+
"title": "Enter a value",
|
|
970
|
+
"placeholder": "type something..."
|
|
971
|
+
}
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
Expected response: `extension_ui_response` with `value` (the entered text) or `cancelled: true`.
|
|
975
|
+
|
|
976
|
+
#### editor
|
|
977
|
+
|
|
978
|
+
Open a multi-line text editor with optional prefilled content.
|
|
979
|
+
|
|
980
|
+
```json
|
|
981
|
+
{
|
|
982
|
+
"type": "extension_ui_request",
|
|
983
|
+
"id": "uuid-4",
|
|
984
|
+
"method": "editor",
|
|
985
|
+
"title": "Edit some text",
|
|
986
|
+
"prefill": "Line 1\nLine 2\nLine 3"
|
|
987
|
+
}
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
Expected response: `extension_ui_response` with `value` (the edited text) or `cancelled: true`.
|
|
991
|
+
|
|
992
|
+
#### notify
|
|
993
|
+
|
|
994
|
+
Display a notification. Fire-and-forget, no response expected.
|
|
995
|
+
|
|
996
|
+
```json
|
|
997
|
+
{
|
|
998
|
+
"type": "extension_ui_request",
|
|
999
|
+
"id": "uuid-5",
|
|
1000
|
+
"method": "notify",
|
|
1001
|
+
"message": "Command blocked by user",
|
|
1002
|
+
"notifyType": "warning"
|
|
1003
|
+
}
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
The `notifyType` field is `"info"`, `"warning"`, or `"error"`. Defaults to `"info"` if omitted.
|
|
1007
|
+
|
|
1008
|
+
#### setStatus
|
|
1009
|
+
|
|
1010
|
+
Set or clear a status entry in the footer/status bar. Fire-and-forget.
|
|
1011
|
+
|
|
1012
|
+
```json
|
|
1013
|
+
{
|
|
1014
|
+
"type": "extension_ui_request",
|
|
1015
|
+
"id": "uuid-6",
|
|
1016
|
+
"method": "setStatus",
|
|
1017
|
+
"statusKey": "my-ext",
|
|
1018
|
+
"statusText": "Turn 3 running..."
|
|
1019
|
+
}
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
Send `statusText: undefined` (or omit it) to clear the status entry for that key.
|
|
1023
|
+
|
|
1024
|
+
#### setWidget
|
|
1025
|
+
|
|
1026
|
+
Set or clear a widget (block of text lines) displayed above or below the editor. Fire-and-forget.
|
|
1027
|
+
|
|
1028
|
+
```json
|
|
1029
|
+
{
|
|
1030
|
+
"type": "extension_ui_request",
|
|
1031
|
+
"id": "uuid-7",
|
|
1032
|
+
"method": "setWidget",
|
|
1033
|
+
"widgetKey": "my-ext",
|
|
1034
|
+
"widgetLines": ["--- My Widget ---", "Line 1", "Line 2"],
|
|
1035
|
+
"widgetPlacement": "aboveEditor"
|
|
1036
|
+
}
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
Send `widgetLines: undefined` (or omit it) to clear the widget. The `widgetPlacement` field is `"aboveEditor"` (default) or `"belowEditor"`. Only string arrays are supported in RPC mode; component factories are ignored.
|
|
1040
|
+
|
|
1041
|
+
#### setTitle
|
|
1042
|
+
|
|
1043
|
+
Set the terminal window/tab title. Fire-and-forget.
|
|
1044
|
+
|
|
1045
|
+
```json
|
|
1046
|
+
{
|
|
1047
|
+
"type": "extension_ui_request",
|
|
1048
|
+
"id": "uuid-8",
|
|
1049
|
+
"method": "setTitle",
|
|
1050
|
+
"title": "pi - my project"
|
|
1051
|
+
}
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
#### set_editor_text
|
|
1055
|
+
|
|
1056
|
+
Set the text in the input editor. Fire-and-forget.
|
|
1057
|
+
|
|
1058
|
+
```json
|
|
1059
|
+
{
|
|
1060
|
+
"type": "extension_ui_request",
|
|
1061
|
+
"id": "uuid-9",
|
|
1062
|
+
"method": "set_editor_text",
|
|
1063
|
+
"text": "prefilled text for the user"
|
|
1064
|
+
}
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
### Extension UI Responses (stdin)
|
|
1068
|
+
|
|
1069
|
+
Responses are sent for dialog methods only (`select`, `confirm`, `input`, `editor`). The `id` must match the request.
|
|
1070
|
+
|
|
1071
|
+
#### Value response (select, input, editor)
|
|
1072
|
+
|
|
1073
|
+
```json
|
|
1074
|
+
{"type": "extension_ui_response", "id": "uuid-1", "value": "Allow"}
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
#### Confirmation response (confirm)
|
|
1078
|
+
|
|
1079
|
+
```json
|
|
1080
|
+
{"type": "extension_ui_response", "id": "uuid-2", "confirmed": true}
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
#### Cancellation response (any dialog)
|
|
1084
|
+
|
|
1085
|
+
Dismiss any dialog method. The extension receives `undefined` (for select/input/editor) or `false` (for confirm).
|
|
1086
|
+
|
|
1087
|
+
```json
|
|
1088
|
+
{"type": "extension_ui_response", "id": "uuid-3", "cancelled": true}
|
|
1089
|
+
```
|
|
1090
|
+
|
|
906
1091
|
## Error Handling
|
|
907
1092
|
|
|
908
1093
|
Failed commands return a response with `success: false`:
|
|
@@ -933,7 +1118,7 @@ Source files:
|
|
|
933
1118
|
- [`packages/ai/src/types.ts`](../../ai/src/types.ts) - `Model`, `UserMessage`, `AssistantMessage`, `ToolResultMessage`
|
|
934
1119
|
- [`packages/agent/src/types.ts`](../../agent/src/types.ts) - `AgentMessage`, `AgentEvent`
|
|
935
1120
|
- [`src/core/messages.ts`](../src/core/messages.ts) - `BashExecutionMessage`
|
|
936
|
-
- [`src/modes/rpc/rpc-types.ts`](../src/modes/rpc/rpc-types.ts) - RPC command/response types
|
|
1121
|
+
- [`src/modes/rpc/rpc-types.ts`](../src/modes/rpc/rpc-types.ts) - RPC command/response types, extension UI request/response types
|
|
937
1122
|
|
|
938
1123
|
### Model
|
|
939
1124
|
|
|
@@ -1082,6 +1267,8 @@ for event in read_events():
|
|
|
1082
1267
|
|
|
1083
1268
|
See [`test/rpc-example.ts`](../test/rpc-example.ts) for a complete interactive example, or [`src/modes/rpc/rpc-client.ts`](../src/modes/rpc/rpc-client.ts) for a typed client implementation.
|
|
1084
1269
|
|
|
1270
|
+
For a complete example of handling the extension UI protocol, see [`examples/rpc-extension-ui.ts`](../examples/rpc-extension-ui.ts) which pairs with the [`examples/extensions/rpc-demo.ts`](../examples/extensions/rpc-demo.ts) extension.
|
|
1271
|
+
|
|
1085
1272
|
```javascript
|
|
1086
1273
|
const { spawn } = require("child_process");
|
|
1087
1274
|
const readline = require("readline");
|
package/docs/termux.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Termux (Android) Setup
|
|
2
|
+
|
|
3
|
+
Pi runs on Android via [Termux](https://termux.dev/), a terminal emulator and Linux environment for Android.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
1. Install [Termux](https://github.com/termux/termux-app#installation) from GitHub or F-Droid (not Google Play, that version is deprecated)
|
|
8
|
+
2. Install [Termux:API](https://github.com/termux/termux-api#installation) from GitHub or F-Droid for clipboard and other device integrations
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Update packages
|
|
14
|
+
pkg update && pkg upgrade
|
|
15
|
+
|
|
16
|
+
# Install dependencies
|
|
17
|
+
pkg install nodejs termux-api git
|
|
18
|
+
|
|
19
|
+
# Install pi
|
|
20
|
+
npm install -g @mariozechner/pi-coding-agent
|
|
21
|
+
|
|
22
|
+
# Create config directory
|
|
23
|
+
mkdir -p ~/.pi/agent
|
|
24
|
+
|
|
25
|
+
# Run pi
|
|
26
|
+
pi
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Clipboard Support
|
|
30
|
+
|
|
31
|
+
Clipboard operations use `termux-clipboard-set` and `termux-clipboard-get` when running in Termux. The Termux:API app must be installed for these to work.
|
|
32
|
+
|
|
33
|
+
Image clipboard is not supported on Termux (the `ctrl+v` image paste feature will not work).
|
|
34
|
+
|
|
35
|
+
## Example AGENTS.md for Termux
|
|
36
|
+
|
|
37
|
+
Create `~/.pi/agent/AGENTS.md` to help the agent understand the Termux environment:
|
|
38
|
+
|
|
39
|
+
```markdown
|
|
40
|
+
# Agent Environment: Termux on Android
|
|
41
|
+
|
|
42
|
+
## Location
|
|
43
|
+
- **OS**: Android (Termux terminal emulator)
|
|
44
|
+
- **Home**: `/data/data/com.termux/files/home`
|
|
45
|
+
- **Prefix**: `/data/data/com.termux/files/usr`
|
|
46
|
+
- **Shared storage**: `/storage/emulated/0` (Downloads, Documents, etc.)
|
|
47
|
+
|
|
48
|
+
## Opening URLs
|
|
49
|
+
```bash
|
|
50
|
+
termux-open-url "https://example.com"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Opening Files
|
|
54
|
+
```bash
|
|
55
|
+
termux-open file.pdf # Opens with default app
|
|
56
|
+
termux-open -c image.jpg # Choose app
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Clipboard
|
|
60
|
+
```bash
|
|
61
|
+
termux-clipboard-set "text" # Copy
|
|
62
|
+
termux-clipboard-get # Paste
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Notifications
|
|
66
|
+
```bash
|
|
67
|
+
termux-notification -t "Title" -c "Content"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Device Info
|
|
71
|
+
```bash
|
|
72
|
+
termux-battery-status # Battery info
|
|
73
|
+
termux-wifi-connectioninfo # WiFi info
|
|
74
|
+
termux-telephony-deviceinfo # Device info
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Sharing
|
|
78
|
+
```bash
|
|
79
|
+
termux-share -a send file.txt # Share file
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Other Useful Commands
|
|
83
|
+
```bash
|
|
84
|
+
termux-toast "message" # Quick toast popup
|
|
85
|
+
termux-vibrate # Vibrate device
|
|
86
|
+
termux-tts-speak "hello" # Text to speech
|
|
87
|
+
termux-camera-photo out.jpg # Take photo
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Notes
|
|
91
|
+
- Termux:API app must be installed for `termux-*` commands
|
|
92
|
+
- Use `pkg install termux-api` for the command-line tools
|
|
93
|
+
- Storage permission needed for `/storage/emulated/0` access
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Limitations
|
|
97
|
+
|
|
98
|
+
- **No image clipboard**: Termux clipboard API only supports text
|
|
99
|
+
- **No native binaries**: Some optional native dependencies (like the clipboard module) are unavailable on Android ARM64 and are skipped during installation
|
|
100
|
+
- **Storage access**: To access files in `/storage/emulated/0` (Downloads, etc.), run `termux-setup-storage` once to grant permissions
|
|
101
|
+
|
|
102
|
+
## Troubleshooting
|
|
103
|
+
|
|
104
|
+
### Clipboard not working
|
|
105
|
+
|
|
106
|
+
Ensure both apps are installed:
|
|
107
|
+
1. Termux (from GitHub or F-Droid)
|
|
108
|
+
2. Termux:API (from GitHub or F-Droid)
|
|
109
|
+
|
|
110
|
+
Then install the CLI tools:
|
|
111
|
+
```bash
|
|
112
|
+
pkg install termux-api
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Permission denied for shared storage
|
|
116
|
+
|
|
117
|
+
Run once to grant storage permissions:
|
|
118
|
+
```bash
|
|
119
|
+
termux-setup-storage
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Node.js installation issues
|
|
123
|
+
|
|
124
|
+
If npm fails, try clearing the cache:
|
|
125
|
+
```bash
|
|
126
|
+
npm cache clean --force
|
|
127
|
+
```
|
|
@@ -53,6 +53,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|
|
|
53
53
|
| `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
|
|
54
54
|
| `send-user-message.ts` | Demonstrates `pi.sendUserMessage()` for sending user messages from extensions |
|
|
55
55
|
| `timed-confirm.ts` | Demonstrates AbortSignal for auto-dismissing `ctx.ui.confirm()` and `ctx.ui.select()` dialogs |
|
|
56
|
+
| `rpc-demo.ts` | Exercises all RPC-supported extension UI methods; pair with [`examples/rpc-extension-ui.ts`](../rpc-extension-ui.ts) |
|
|
56
57
|
| `modal-editor.ts` | Custom vim-like modal editor via `ctx.ui.setEditorComponent()` |
|
|
57
58
|
| `rainbow-editor.ts` | Animated rainbow text effect via custom editor |
|
|
58
59
|
| `notify.ts` | Desktop notifications via OSC 777 when agent finishes (Ghostty, iTerm2, WezTerm) |
|
|
@@ -352,7 +352,7 @@ export default function antigravityImageGen(pi: ExtensionAPI) {
|
|
|
352
352
|
description:
|
|
353
353
|
"Generate an image via Google Antigravity image models. Returns the image as a tool result attachment. Optional saving via save=project|global|custom|none, or PI_IMAGE_SAVE_MODE/PI_IMAGE_SAVE_DIR.",
|
|
354
354
|
parameters: TOOL_PARAMS,
|
|
355
|
-
async execute(_toolCallId, params: ToolParams, onUpdate, ctx
|
|
355
|
+
async execute(_toolCallId, params: ToolParams, signal, onUpdate, ctx) {
|
|
356
356
|
const { accessToken, projectId } = await getCredentials(ctx);
|
|
357
357
|
const model = params.model || DEFAULT_MODEL;
|
|
358
358
|
const aspectRatio = params.aspectRatio || DEFAULT_ASPECT_RATIO;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bash Spawn Hook Example
|
|
3
|
+
*
|
|
4
|
+
* Adjusts command, cwd, and env before execution.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* pi -e ./bash-spawn-hook.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
11
|
+
import { createBashTool } from "@mariozechner/pi-coding-agent";
|
|
12
|
+
|
|
13
|
+
export default function (pi: ExtensionAPI) {
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
|
|
16
|
+
const bashTool = createBashTool(cwd, {
|
|
17
|
+
spawnHook: ({ command, cwd, env }) => ({
|
|
18
|
+
command: `source ~/.profile\n${command}`,
|
|
19
|
+
cwd,
|
|
20
|
+
env: { ...env, PI_SPAWN_HOOK: "1" },
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
pi.registerTool({
|
|
25
|
+
...bashTool,
|
|
26
|
+
execute: async (id, params, signal, onUpdate, _ctx) => {
|
|
27
|
+
return bashTool.execute(id, params, signal, onUpdate);
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-custom-provider",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-custom-provider",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.2.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.52.0"
|
|
12
12
|
}
|
|
@@ -14,7 +14,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
14
14
|
name: Type.String({ description: "Name to greet" }),
|
|
15
15
|
}),
|
|
16
16
|
|
|
17
|
-
async execute(_toolCallId, params, _onUpdate, _ctx
|
|
17
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
18
18
|
const { name } = params as { name: string };
|
|
19
19
|
return {
|
|
20
20
|
content: [{ type: "text", text: `Hello, ${name}!` }],
|
|
@@ -40,7 +40,7 @@ export default function question(pi: ExtensionAPI) {
|
|
|
40
40
|
description: "Ask the user a question and let them pick from options. Use when you need user input to proceed.",
|
|
41
41
|
parameters: QuestionParams,
|
|
42
42
|
|
|
43
|
-
async execute(_toolCallId, params, _onUpdate, ctx
|
|
43
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
44
44
|
if (!ctx.hasUI) {
|
|
45
45
|
return {
|
|
46
46
|
content: [{ type: "text", text: "Error: UI not available (running in non-interactive mode)" }],
|
|
@@ -81,7 +81,7 @@ export default function questionnaire(pi: ExtensionAPI) {
|
|
|
81
81
|
"Ask the user one or more questions. Use for clarifying requirements, getting preferences, or confirming decisions. For single questions, shows a simple option list. For multiple questions, shows a tab-based interface.",
|
|
82
82
|
parameters: QuestionnaireParams,
|
|
83
83
|
|
|
84
|
-
async execute(_toolCallId, params, _onUpdate, ctx
|
|
84
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
85
85
|
if (!ctx.hasUI) {
|
|
86
86
|
return errorResult("Error: UI not available (running in non-interactive mode)");
|
|
87
87
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPC Extension UI Demo
|
|
3
|
+
*
|
|
4
|
+
* Purpose-built extension that exercises all RPC-supported extension UI methods.
|
|
5
|
+
* Designed to be loaded alongside the rpc-extension-ui-example.ts script to
|
|
6
|
+
* demonstrate the full extension UI protocol.
|
|
7
|
+
*
|
|
8
|
+
* UI methods exercised:
|
|
9
|
+
* - select() - on tool_call for dangerous bash commands
|
|
10
|
+
* - confirm() - on session_before_switch
|
|
11
|
+
* - input() - via /rpc-input command
|
|
12
|
+
* - editor() - via /rpc-editor command
|
|
13
|
+
* - notify() - after each dialog completes
|
|
14
|
+
* - setStatus() - on turn_start/turn_end
|
|
15
|
+
* - setWidget() - on session_start
|
|
16
|
+
* - setTitle() - on session_start and session_switch
|
|
17
|
+
* - setEditorText() - via /rpc-prefill command
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
21
|
+
|
|
22
|
+
export default function (pi: ExtensionAPI) {
|
|
23
|
+
let turnCount = 0;
|
|
24
|
+
|
|
25
|
+
// -- setTitle, setWidget, setStatus on session lifecycle --
|
|
26
|
+
|
|
27
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
28
|
+
ctx.ui.setTitle("pi RPC Demo");
|
|
29
|
+
ctx.ui.setWidget("rpc-demo", ["--- RPC Extension UI Demo ---", "Loaded and ready."]);
|
|
30
|
+
ctx.ui.setStatus("rpc-demo", `Turns: ${turnCount}`);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
pi.on("session_switch", async (_event, ctx) => {
|
|
34
|
+
turnCount = 0;
|
|
35
|
+
ctx.ui.setTitle("pi RPC Demo (new session)");
|
|
36
|
+
ctx.ui.setStatus("rpc-demo", `Turns: ${turnCount}`);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// -- setStatus on turn lifecycle --
|
|
40
|
+
|
|
41
|
+
pi.on("turn_start", async (_event, ctx) => {
|
|
42
|
+
turnCount++;
|
|
43
|
+
ctx.ui.setStatus("rpc-demo", `Turn ${turnCount} running...`);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
pi.on("turn_end", async (_event, ctx) => {
|
|
47
|
+
ctx.ui.setStatus("rpc-demo", `Turn ${turnCount} done`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// -- select on dangerous tool calls --
|
|
51
|
+
|
|
52
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
53
|
+
if (event.toolName !== "bash") return undefined;
|
|
54
|
+
|
|
55
|
+
const command = event.input.command as string;
|
|
56
|
+
const isDangerous = /\brm\s+(-rf?|--recursive)/i.test(command) || /\bsudo\b/i.test(command);
|
|
57
|
+
|
|
58
|
+
if (isDangerous) {
|
|
59
|
+
if (!ctx.hasUI) {
|
|
60
|
+
return { block: true, reason: "Dangerous command blocked (no UI)" };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const choice = await ctx.ui.select(`Dangerous command: ${command}`, ["Allow", "Block"]);
|
|
64
|
+
if (choice !== "Allow") {
|
|
65
|
+
ctx.ui.notify("Command blocked by user", "warning");
|
|
66
|
+
return { block: true, reason: "Blocked by user" };
|
|
67
|
+
}
|
|
68
|
+
ctx.ui.notify("Command allowed", "info");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return undefined;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// -- confirm on session clear --
|
|
75
|
+
|
|
76
|
+
pi.on("session_before_switch", async (event, ctx) => {
|
|
77
|
+
if (event.reason !== "new") return;
|
|
78
|
+
if (!ctx.hasUI) return;
|
|
79
|
+
|
|
80
|
+
const confirmed = await ctx.ui.confirm("Clear session?", "All messages will be lost.");
|
|
81
|
+
if (!confirmed) {
|
|
82
|
+
ctx.ui.notify("Clear cancelled", "info");
|
|
83
|
+
return { cancel: true };
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// -- input via command --
|
|
88
|
+
|
|
89
|
+
pi.registerCommand("rpc-input", {
|
|
90
|
+
description: "Prompt for text input (demonstrates ctx.ui.input in RPC)",
|
|
91
|
+
handler: async (_args, ctx) => {
|
|
92
|
+
const value = await ctx.ui.input("Enter a value", "type something...");
|
|
93
|
+
if (value) {
|
|
94
|
+
ctx.ui.notify(`You entered: ${value}`, "info");
|
|
95
|
+
} else {
|
|
96
|
+
ctx.ui.notify("Input cancelled", "info");
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// -- editor via command --
|
|
102
|
+
|
|
103
|
+
pi.registerCommand("rpc-editor", {
|
|
104
|
+
description: "Open multi-line editor (demonstrates ctx.ui.editor in RPC)",
|
|
105
|
+
handler: async (_args, ctx) => {
|
|
106
|
+
const text = await ctx.ui.editor("Edit some text", "Line 1\nLine 2\nLine 3");
|
|
107
|
+
if (text) {
|
|
108
|
+
ctx.ui.notify(`Editor submitted (${text.split("\n").length} lines)`, "info");
|
|
109
|
+
} else {
|
|
110
|
+
ctx.ui.notify("Editor cancelled", "info");
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// -- setEditorText via command --
|
|
116
|
+
|
|
117
|
+
pi.registerCommand("rpc-prefill", {
|
|
118
|
+
description: "Prefill the input editor (demonstrates ctx.ui.setEditorText in RPC)",
|
|
119
|
+
handler: async (_args, ctx) => {
|
|
120
|
+
ctx.ui.setEditorText("This text was set by the rpc-demo extension.");
|
|
121
|
+
ctx.ui.notify("Editor prefilled", "info");
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
@@ -211,7 +211,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
211
211
|
pi.registerTool({
|
|
212
212
|
...localBash,
|
|
213
213
|
label: "bash (sandboxed)",
|
|
214
|
-
async execute(id, params, onUpdate, _ctx
|
|
214
|
+
async execute(id, params, signal, onUpdate, _ctx) {
|
|
215
215
|
if (!sandboxEnabled || !sandboxInitialized) {
|
|
216
216
|
return localBash.execute(id, params, signal, onUpdate);
|
|
217
217
|
}
|
|
@@ -23,7 +23,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
23
23
|
label: "Finish and Exit",
|
|
24
24
|
description: "Complete a task and exit pi",
|
|
25
25
|
parameters: Type.Object({}),
|
|
26
|
-
async execute(_toolCallId, _params, _onUpdate, ctx
|
|
26
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
27
27
|
// Do any final work here...
|
|
28
28
|
// Request graceful shutdown (deferred until agent is idle)
|
|
29
29
|
ctx.shutdown();
|
|
@@ -44,7 +44,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
44
44
|
parameters: Type.Object({
|
|
45
45
|
environment: Type.String({ description: "Target environment (e.g., production, staging)" }),
|
|
46
46
|
}),
|
|
47
|
-
async execute(_toolCallId, params, onUpdate, ctx
|
|
47
|
+
async execute(_toolCallId, params, _signal, onUpdate, ctx) {
|
|
48
48
|
onUpdate?.({ content: [{ type: "text", text: `Deploying to ${params.environment}...` }], details: {} });
|
|
49
49
|
|
|
50
50
|
// Example deployment logic
|
|
@@ -127,7 +127,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
127
127
|
|
|
128
128
|
pi.registerTool({
|
|
129
129
|
...localRead,
|
|
130
|
-
async execute(id, params, onUpdate, _ctx
|
|
130
|
+
async execute(id, params, signal, onUpdate, _ctx) {
|
|
131
131
|
const ssh = getSsh();
|
|
132
132
|
if (ssh) {
|
|
133
133
|
const tool = createReadTool(localCwd, {
|
|
@@ -141,7 +141,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
141
141
|
|
|
142
142
|
pi.registerTool({
|
|
143
143
|
...localWrite,
|
|
144
|
-
async execute(id, params, onUpdate, _ctx
|
|
144
|
+
async execute(id, params, signal, onUpdate, _ctx) {
|
|
145
145
|
const ssh = getSsh();
|
|
146
146
|
if (ssh) {
|
|
147
147
|
const tool = createWriteTool(localCwd, {
|
|
@@ -155,7 +155,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
155
155
|
|
|
156
156
|
pi.registerTool({
|
|
157
157
|
...localEdit,
|
|
158
|
-
async execute(id, params, onUpdate, _ctx
|
|
158
|
+
async execute(id, params, signal, onUpdate, _ctx) {
|
|
159
159
|
const ssh = getSsh();
|
|
160
160
|
if (ssh) {
|
|
161
161
|
const tool = createEditTool(localCwd, {
|
|
@@ -169,7 +169,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
169
169
|
|
|
170
170
|
pi.registerTool({
|
|
171
171
|
...localBash,
|
|
172
|
-
async execute(id, params, onUpdate, _ctx
|
|
172
|
+
async execute(id, params, signal, onUpdate, _ctx) {
|
|
173
173
|
const ssh = getSsh();
|
|
174
174
|
if (ssh) {
|
|
175
175
|
const tool = createBashTool(localCwd, {
|
|
@@ -416,7 +416,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
416
416
|
].join(" "),
|
|
417
417
|
parameters: SubagentParams,
|
|
418
418
|
|
|
419
|
-
async execute(_toolCallId, params, onUpdate, ctx
|
|
419
|
+
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
420
420
|
const agentScope: AgentScope = params.agentScope ?? "user";
|
|
421
421
|
const discovery = discoverAgents(ctx.cwd, agentScope);
|
|
422
422
|
const agents = discovery.agents;
|
|
@@ -141,7 +141,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
141
141
|
description: "Manage a todo list. Actions: list, add (text), toggle (id), clear",
|
|
142
142
|
parameters: TodoParams,
|
|
143
143
|
|
|
144
|
-
async execute(_toolCallId, params, _onUpdate, _ctx
|
|
144
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
145
145
|
switch (params.action) {
|
|
146
146
|
case "list":
|
|
147
147
|
return {
|
|
@@ -72,7 +72,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
72
72
|
"Read the contents of a file with access logging. Some sensitive paths (.env, secrets, credentials) are blocked.",
|
|
73
73
|
parameters: readSchema,
|
|
74
74
|
|
|
75
|
-
async execute(_toolCallId, params, _onUpdate, ctx) {
|
|
75
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
76
76
|
const { path, offset, limit } = params;
|
|
77
77
|
const absolutePath = resolve(ctx.cwd, path);
|
|
78
78
|
|