@vaclav-synacek/pi-coding-agent-termux 0.50.9-0 → 0.51.1-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/cli/args.d.ts.map +1 -1
  3. package/dist/cli/args.js +1 -0
  4. package/dist/cli/args.js.map +1 -1
  5. package/dist/cli/session-picker.d.ts.map +1 -1
  6. package/dist/cli/session-picker.js +3 -1
  7. package/dist/cli/session-picker.js.map +1 -1
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +9 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/core/agent-session.d.ts +1 -0
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +24 -9
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/extensions/index.d.ts +3 -3
  16. package/dist/core/extensions/index.d.ts.map +1 -1
  17. package/dist/core/extensions/index.js +1 -1
  18. package/dist/core/extensions/index.js.map +1 -1
  19. package/dist/core/extensions/runner.d.ts +4 -0
  20. package/dist/core/extensions/runner.d.ts.map +1 -1
  21. package/dist/core/extensions/runner.js +4 -0
  22. package/dist/core/extensions/runner.js.map +1 -1
  23. package/dist/core/extensions/types.d.ts +74 -5
  24. package/dist/core/extensions/types.d.ts.map +1 -1
  25. package/dist/core/extensions/types.js +4 -1
  26. package/dist/core/extensions/types.js.map +1 -1
  27. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  28. package/dist/core/extensions/wrapper.js +1 -1
  29. package/dist/core/extensions/wrapper.js.map +1 -1
  30. package/dist/core/keybindings.d.ts +1 -1
  31. package/dist/core/keybindings.d.ts.map +1 -1
  32. package/dist/core/keybindings.js +2 -0
  33. package/dist/core/keybindings.js.map +1 -1
  34. package/dist/core/model-registry.d.ts.map +1 -1
  35. package/dist/core/model-registry.js +19 -17
  36. package/dist/core/model-registry.js.map +1 -1
  37. package/dist/core/package-manager.d.ts.map +1 -1
  38. package/dist/core/package-manager.js +11 -9
  39. package/dist/core/package-manager.js.map +1 -1
  40. package/dist/core/settings-manager.d.ts +3 -0
  41. package/dist/core/settings-manager.d.ts.map +1 -1
  42. package/dist/core/settings-manager.js +15 -0
  43. package/dist/core/settings-manager.js.map +1 -1
  44. package/dist/core/skills.d.ts.map +1 -1
  45. package/dist/core/skills.js +1 -0
  46. package/dist/core/skills.js.map +1 -1
  47. package/dist/core/tools/bash.d.ts +11 -0
  48. package/dist/core/tools/bash.d.ts.map +1 -1
  49. package/dist/core/tools/bash.js +18 -3
  50. package/dist/core/tools/bash.js.map +1 -1
  51. package/dist/core/tools/edit.d.ts +2 -0
  52. package/dist/core/tools/edit.d.ts.map +1 -1
  53. package/dist/core/tools/edit.js.map +1 -1
  54. package/dist/core/tools/find.d.ts +2 -0
  55. package/dist/core/tools/find.d.ts.map +1 -1
  56. package/dist/core/tools/find.js.map +1 -1
  57. package/dist/core/tools/grep.d.ts +2 -0
  58. package/dist/core/tools/grep.d.ts.map +1 -1
  59. package/dist/core/tools/grep.js.map +1 -1
  60. package/dist/core/tools/index.d.ts +7 -7
  61. package/dist/core/tools/index.d.ts.map +1 -1
  62. package/dist/core/tools/index.js +5 -5
  63. package/dist/core/tools/index.js.map +1 -1
  64. package/dist/core/tools/ls.d.ts +2 -0
  65. package/dist/core/tools/ls.d.ts.map +1 -1
  66. package/dist/core/tools/ls.js.map +1 -1
  67. package/dist/core/tools/read.d.ts +2 -0
  68. package/dist/core/tools/read.d.ts.map +1 -1
  69. package/dist/core/tools/read.js.map +1 -1
  70. package/dist/core/tools/write.d.ts +2 -0
  71. package/dist/core/tools/write.d.ts.map +1 -1
  72. package/dist/core/tools/write.js.map +1 -1
  73. package/dist/index.d.ts +3 -3
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +1 -1
  76. package/dist/index.js.map +1 -1
  77. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  78. package/dist/modes/interactive/components/custom-message.js +0 -7
  79. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  80. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  81. package/dist/modes/interactive/components/scoped-models-selector.js +4 -1
  82. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  83. package/dist/modes/interactive/components/session-selector-search.d.ts +3 -1
  84. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
  85. package/dist/modes/interactive/components/session-selector-search.js +13 -4
  86. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  87. package/dist/modes/interactive/components/session-selector.d.ts +11 -2
  88. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  89. package/dist/modes/interactive/components/session-selector.js +58 -12
  90. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  91. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  92. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  93. package/dist/modes/interactive/components/settings-selector.js +12 -0
  94. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  95. package/dist/modes/interactive/components/tree-selector.d.ts +6 -0
  96. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  97. package/dist/modes/interactive/components/tree-selector.js +43 -16
  98. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  99. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  100. package/dist/modes/interactive/interactive-mode.js +18 -15
  101. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  102. package/dist/modes/print-mode.d.ts.map +1 -1
  103. package/dist/modes/print-mode.js +4 -0
  104. package/dist/modes/print-mode.js.map +1 -1
  105. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  106. package/dist/modes/rpc/rpc-mode.js +4 -0
  107. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  108. package/dist/utils/clipboard-native.d.ts +7 -0
  109. package/dist/utils/clipboard-native.d.ts.map +1 -0
  110. package/dist/utils/clipboard-native.js +14 -0
  111. package/dist/utils/clipboard-native.js.map +1 -0
  112. package/dist/utils/tools-manager.d.ts.map +1 -1
  113. package/dist/utils/tools-manager.js +14 -0
  114. package/dist/utils/tools-manager.js.map +1 -1
  115. package/docs/extensions.md +57 -9
  116. package/docs/keybindings.md +1 -0
  117. package/docs/models.md +43 -14
  118. package/docs/rpc.md +188 -1
  119. package/docs/termux.md +127 -0
  120. package/examples/extensions/README.md +1 -0
  121. package/examples/extensions/antigravity-image-gen.ts +1 -1
  122. package/examples/extensions/bash-spawn-hook.ts +30 -0
  123. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  124. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  125. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  126. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  127. package/examples/extensions/hello.ts +1 -1
  128. package/examples/extensions/question.ts +1 -1
  129. package/examples/extensions/questionnaire.ts +1 -1
  130. package/examples/extensions/rpc-demo.ts +124 -0
  131. package/examples/extensions/sandbox/index.ts +1 -1
  132. package/examples/extensions/shutdown-command.ts +2 -2
  133. package/examples/extensions/ssh.ts +4 -4
  134. package/examples/extensions/subagent/index.ts +1 -1
  135. package/examples/extensions/todo.ts +1 -1
  136. package/examples/extensions/tool-override.ts +1 -1
  137. package/examples/extensions/truncated-tool.ts +1 -1
  138. package/examples/extensions/with-deps/package-lock.json +2 -2
  139. package/examples/extensions/with-deps/package.json +1 -1
  140. package/examples/rpc-extension-ui.ts +632 -0
  141. package/examples/sdk/06-extensions.ts +1 -1
  142. package/package.json +5 -5
@@ -79,7 +79,7 @@ export default function (pi: ExtensionAPI) {
79
79
  parameters: Type.Object({
80
80
  name: Type.String({ description: "Name to greet" }),
81
81
  }),
82
- async execute(toolCallId, params, onUpdate, ctx, signal) {
82
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
83
83
  return {
84
84
  content: [{ type: "text", text: `Hello, ${params.name}!` }],
85
85
  details: {},
@@ -473,16 +473,49 @@ Use this to update UI elements (status bars, footers) or perform model-specific
473
473
 
474
474
  #### tool_call
475
475
 
476
- Fired before tool executes. **Can block.**
476
+ Fired before tool executes. **Can block.** Use `isToolCallEventType` to narrow and get typed inputs.
477
477
 
478
478
  ```typescript
479
+ import { isToolCallEventType } from "@mariozechner/pi-coding-agent";
480
+
479
481
  pi.on("tool_call", async (event, ctx) => {
480
482
  // event.toolName - "bash", "read", "write", "edit", etc.
481
483
  // event.toolCallId
482
484
  // event.input - tool parameters
483
485
 
484
- if (shouldBlock(event)) {
485
- return { block: true, reason: "Not allowed" };
486
+ // Built-in tools: no type params needed
487
+ if (isToolCallEventType("bash", event)) {
488
+ // event.input is { command: string; timeout?: number }
489
+ if (event.input.command.includes("rm -rf")) {
490
+ return { block: true, reason: "Dangerous command" };
491
+ }
492
+ }
493
+
494
+ if (isToolCallEventType("read", event)) {
495
+ // event.input is { path: string; offset?: number; limit?: number }
496
+ console.log(`Reading: ${event.input.path}`);
497
+ }
498
+ });
499
+ ```
500
+
501
+ #### Typing custom tool input
502
+
503
+ Custom tools should export their input type:
504
+
505
+ ```typescript
506
+ // my-extension.ts
507
+ export type MyToolInput = Static<typeof myToolSchema>;
508
+ ```
509
+
510
+ Use `isToolCallEventType` with explicit type parameters:
511
+
512
+ ```typescript
513
+ import { isToolCallEventType } from "@mariozechner/pi-coding-agent";
514
+ import type { MyToolInput } from "my-extension";
515
+
516
+ pi.on("tool_call", (event) => {
517
+ if (isToolCallEventType<"my_tool", MyToolInput>("my_tool", event)) {
518
+ event.input.action; // typed
486
519
  }
487
520
  });
488
521
  ```
@@ -756,7 +789,7 @@ pi.registerTool({
756
789
  text: Type.Optional(Type.String()),
757
790
  }),
758
791
 
759
- async execute(toolCallId, params, onUpdate, ctx, signal) {
792
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
760
793
  // Stream progress
761
794
  onUpdate?.({ content: [{ type: "text", text: "Working..." }] });
762
795
 
@@ -1082,7 +1115,7 @@ export default function (pi: ExtensionAPI) {
1082
1115
  pi.registerTool({
1083
1116
  name: "my_tool",
1084
1117
  // ...
1085
- async execute(toolCallId, params, onUpdate, ctx, signal) {
1118
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
1086
1119
  items.push("new item");
1087
1120
  return {
1088
1121
  content: [{ type: "text", text: "Added" }],
@@ -1113,7 +1146,7 @@ pi.registerTool({
1113
1146
  text: Type.Optional(Type.String()),
1114
1147
  }),
1115
1148
 
1116
- async execute(toolCallId, params, onUpdate, ctx, signal) {
1149
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
1117
1150
  // Check for cancellation
1118
1151
  if (signal?.aborted) {
1119
1152
  return { content: [{ type: "text", text: "Cancelled" }] };
@@ -1191,7 +1224,7 @@ const remoteRead = createReadTool(cwd, {
1191
1224
  // Register, checking flag at execution time
1192
1225
  pi.registerTool({
1193
1226
  ...remoteRead,
1194
- async execute(id, params, onUpdate, _ctx, signal) {
1227
+ async execute(id, params, signal, onUpdate, _ctx) {
1195
1228
  const ssh = getSshConfig();
1196
1229
  if (ssh) {
1197
1230
  const tool = createReadTool(cwd, { operations: createRemoteOps(ssh) });
@@ -1204,6 +1237,20 @@ pi.registerTool({
1204
1237
 
1205
1238
  **Operations interfaces:** `ReadOperations`, `WriteOperations`, `EditOperations`, `BashOperations`, `LsOperations`, `GrepOperations`, `FindOperations`
1206
1239
 
1240
+ The bash tool also supports a spawn hook to adjust the command, cwd, or env before execution:
1241
+
1242
+ ```typescript
1243
+ import { createBashTool } from "@mariozechner/pi-coding-agent";
1244
+
1245
+ const bashTool = createBashTool(cwd, {
1246
+ spawnHook: ({ command, cwd, env }) => ({
1247
+ command: `source ~/.profile\n${command}`,
1248
+ cwd: `/mnt/sandbox${cwd}`,
1249
+ env: { ...env, CI: "1" },
1250
+ }),
1251
+ });
1252
+ ```
1253
+
1207
1254
  See [examples/extensions/ssh.ts](../examples/extensions/ssh.ts) for a complete SSH example with `--ssh` flag.
1208
1255
 
1209
1256
  ### Output Truncation
@@ -1225,7 +1272,7 @@ import {
1225
1272
  DEFAULT_MAX_LINES, // 2000
1226
1273
  } from "@mariozechner/pi-coding-agent";
1227
1274
 
1228
- async execute(toolCallId, params, onUpdate, ctx, signal) {
1275
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
1229
1276
  const output = await runCommand();
1230
1277
 
1231
1278
  // Apply truncation
@@ -1744,4 +1791,5 @@ All examples in [examples/extensions/](../examples/extensions/).
1744
1791
  | **Misc** |||
1745
1792
  | `antigravity-image-gen.ts` | Image generation tool | `registerTool`, Google Antigravity |
1746
1793
  | `inline-bash.ts` | Inline bash in tool calls | `on("tool_call")` |
1794
+ | `bash-spawn-hook.ts` | Adjust bash command, cwd, and env before execution | `createBashTool`, `spawnHook` |
1747
1795
  | `with-deps/` | Extension with npm dependencies | Package structure with `package.json` |
@@ -124,6 +124,7 @@ Modifier combinations: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, etc.
124
124
  |--------|---------|-------------|
125
125
  | `toggleSessionPath` | `ctrl+p` | Toggle path display |
126
126
  | `toggleSessionSort` | `ctrl+s` | Toggle sort mode |
127
+ | `toggleSessionNamedFilter` | `ctrl+n` | Toggle named-only filter |
127
128
  | `renameSession` | `ctrl+r` | Rename session |
128
129
  | `deleteSession` | `ctrl+d` | Delete session |
129
130
  | `deleteSessionNoninvasive` | `ctrl+backspace` | Delete session (when query empty) |
package/docs/models.md CHANGED
@@ -4,14 +4,39 @@ Add custom providers and models (Ollama, vLLM, LM Studio, proxies) via `~/.pi/ag
4
4
 
5
5
  ## Table of Contents
6
6
 
7
- - [Basic Example](#basic-example)
7
+ - [Minimal Example](#minimal-example)
8
+ - [Full Example](#full-example)
8
9
  - [Supported APIs](#supported-apis)
9
10
  - [Provider Configuration](#provider-configuration)
10
11
  - [Model Configuration](#model-configuration)
11
12
  - [Overriding Built-in Providers](#overriding-built-in-providers)
12
13
  - [OpenAI Compatibility](#openai-compatibility)
13
14
 
14
- ## Basic Example
15
+ ## Minimal Example
16
+
17
+ For local models (Ollama, LM Studio, vLLM), only `id` is required per model:
18
+
19
+ ```json
20
+ {
21
+ "providers": {
22
+ "ollama": {
23
+ "baseUrl": "http://localhost:11434/v1",
24
+ "api": "openai-completions",
25
+ "apiKey": "ollama",
26
+ "models": [
27
+ { "id": "llama3.1:8b" },
28
+ { "id": "qwen2.5-coder:7b" }
29
+ ]
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ The `apiKey` is required but Ollama ignores it, so any value works.
36
+
37
+ ## Full Example
38
+
39
+ Override defaults when you need specific values:
15
40
 
16
41
  ```json
17
42
  {
@@ -19,12 +44,16 @@ Add custom providers and models (Ollama, vLLM, LM Studio, proxies) via `~/.pi/ag
19
44
  "ollama": {
20
45
  "baseUrl": "http://localhost:11434/v1",
21
46
  "api": "openai-completions",
47
+ "apiKey": "ollama",
22
48
  "models": [
23
49
  {
24
- "id": "llama-3.1-8b",
50
+ "id": "llama3.1:8b",
25
51
  "name": "Llama 3.1 8B (Local)",
52
+ "reasoning": false,
53
+ "input": ["text"],
26
54
  "contextWindow": 128000,
27
- "maxTokens": 32000
55
+ "maxTokens": 32000,
56
+ "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
28
57
  }
29
58
  ]
30
59
  }
@@ -95,16 +124,16 @@ The `apiKey` and `headers` fields support three formats:
95
124
 
96
125
  ## Model Configuration
97
126
 
98
- | Field | Required | Description |
99
- |-------|----------|-------------|
100
- | `id` | Yes | Model identifier |
101
- | `name` | No | Display name |
102
- | `api` | No | Override provider's API for this model |
103
- | `contextWindow` | No | Context window size in tokens |
104
- | `maxTokens` | No | Maximum output tokens |
105
- | `reasoning` | No | Supports extended thinking |
106
- | `input` | No | Input types: `["text"]` or `["text", "image"]` |
107
- | `cost` | No | `{"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}` |
127
+ | Field | Required | Default | Description |
128
+ |-------|----------|---------|-------------|
129
+ | `id` | Yes | — | Model identifier (passed to the API) |
130
+ | `name` | No | `id` | Display name in model selector |
131
+ | `api` | No | provider's `api` | Override provider's API for this model |
132
+ | `reasoning` | No | `false` | Supports extended thinking |
133
+ | `input` | No | `["text"]` | Input types: `["text"]` or `["text", "image"]` |
134
+ | `contextWindow` | No | `128000` | Context window size in tokens |
135
+ | `maxTokens` | No | `16384` | Maximum output tokens |
136
+ | `cost` | No | all zeros | `{"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}` (per million tokens) |
108
137
 
109
138
  ## Overriding Built-in Providers
110
139
 
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, signal) {
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.1.9",
3
+ "version": "1.2.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "1.1.9",
9
+ "version": "1.2.1",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "1.1.9",
4
+ "version": "1.2.1",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "1.1.9",
4
+ "version": "1.2.1",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-qwen-cli",
3
3
  "private": true,
4
- "version": "1.0.2",
4
+ "version": "1.1.1",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -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, _signal) {
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, _signal) {
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)" }],