@earendil-works/pi-coding-agent 0.78.0 → 0.79.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 (203) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +26 -6
  3. package/dist/cli/args.d.ts +1 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +15 -2
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +9 -1
  9. package/dist/config.js.map +1 -1
  10. package/dist/core/agent-session-runtime.d.ts +3 -1
  11. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  12. package/dist/core/agent-session-runtime.js +1 -0
  13. package/dist/core/agent-session-runtime.js.map +1 -1
  14. package/dist/core/agent-session-services.d.ts +2 -1
  15. package/dist/core/agent-session-services.d.ts.map +1 -1
  16. package/dist/core/agent-session-services.js +2 -2
  17. package/dist/core/agent-session-services.js.map +1 -1
  18. package/dist/core/agent-session.d.ts +3 -1
  19. package/dist/core/agent-session.d.ts.map +1 -1
  20. package/dist/core/agent-session.js +7 -1
  21. package/dist/core/agent-session.js.map +1 -1
  22. package/dist/core/auth-storage.d.ts.map +1 -1
  23. package/dist/core/auth-storage.js +4 -3
  24. package/dist/core/auth-storage.js.map +1 -1
  25. package/dist/core/compaction/branch-summarization.d.ts +3 -1
  26. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  27. package/dist/core/compaction/branch-summarization.js +9 -3
  28. package/dist/core/compaction/branch-summarization.js.map +1 -1
  29. package/dist/core/compaction/utils.d.ts +1 -1
  30. package/dist/core/compaction/utils.d.ts.map +1 -1
  31. package/dist/core/compaction/utils.js +1 -1
  32. package/dist/core/compaction/utils.js.map +1 -1
  33. package/dist/core/export-html/template.js +19 -6
  34. package/dist/core/extensions/index.d.ts +1 -1
  35. package/dist/core/extensions/index.d.ts.map +1 -1
  36. package/dist/core/extensions/index.js.map +1 -1
  37. package/dist/core/extensions/loader.d.ts +1 -1
  38. package/dist/core/extensions/loader.d.ts.map +1 -1
  39. package/dist/core/extensions/loader.js +4 -4
  40. package/dist/core/extensions/loader.js.map +1 -1
  41. package/dist/core/extensions/runner.d.ts +9 -3
  42. package/dist/core/extensions/runner.d.ts.map +1 -1
  43. package/dist/core/extensions/runner.js +41 -1
  44. package/dist/core/extensions/runner.js.map +1 -1
  45. package/dist/core/extensions/types.d.ts +25 -2
  46. package/dist/core/extensions/types.d.ts.map +1 -1
  47. package/dist/core/extensions/types.js.map +1 -1
  48. package/dist/core/footer-data-provider.d.ts +2 -0
  49. package/dist/core/footer-data-provider.d.ts.map +1 -1
  50. package/dist/core/footer-data-provider.js +29 -1
  51. package/dist/core/footer-data-provider.js.map +1 -1
  52. package/dist/core/model-registry.d.ts.map +1 -1
  53. package/dist/core/model-registry.js +1 -0
  54. package/dist/core/model-registry.js.map +1 -1
  55. package/dist/core/model-resolver.d.ts.map +1 -1
  56. package/dist/core/model-resolver.js +3 -0
  57. package/dist/core/model-resolver.js.map +1 -1
  58. package/dist/core/package-manager.d.ts +3 -0
  59. package/dist/core/package-manager.d.ts.map +1 -1
  60. package/dist/core/package-manager.js +47 -13
  61. package/dist/core/package-manager.js.map +1 -1
  62. package/dist/core/provider-attribution.d.ts +4 -0
  63. package/dist/core/provider-attribution.d.ts.map +1 -0
  64. package/dist/core/provider-attribution.js +72 -0
  65. package/dist/core/provider-attribution.js.map +1 -0
  66. package/dist/core/provider-display-names.d.ts.map +1 -1
  67. package/dist/core/provider-display-names.js +3 -0
  68. package/dist/core/provider-display-names.js.map +1 -1
  69. package/dist/core/resource-loader.d.ts +13 -2
  70. package/dist/core/resource-loader.d.ts.map +1 -1
  71. package/dist/core/resource-loader.js +129 -54
  72. package/dist/core/resource-loader.js.map +1 -1
  73. package/dist/core/sdk.d.ts.map +1 -1
  74. package/dist/core/sdk.js +7 -33
  75. package/dist/core/sdk.js.map +1 -1
  76. package/dist/core/session-manager.d.ts.map +1 -1
  77. package/dist/core/session-manager.js +92 -68
  78. package/dist/core/session-manager.js.map +1 -1
  79. package/dist/core/settings-manager.d.ts +10 -2
  80. package/dist/core/settings-manager.d.ts.map +1 -1
  81. package/dist/core/settings-manager.js +71 -30
  82. package/dist/core/settings-manager.js.map +1 -1
  83. package/dist/core/slash-commands.d.ts.map +1 -1
  84. package/dist/core/slash-commands.js +1 -0
  85. package/dist/core/slash-commands.js.map +1 -1
  86. package/dist/core/tools/bash.d.ts.map +1 -1
  87. package/dist/core/tools/bash.js +1 -1
  88. package/dist/core/tools/bash.js.map +1 -1
  89. package/dist/core/tools/find.d.ts.map +1 -1
  90. package/dist/core/tools/find.js +1 -1
  91. package/dist/core/tools/find.js.map +1 -1
  92. package/dist/core/tools/grep.d.ts.map +1 -1
  93. package/dist/core/tools/grep.js +1 -1
  94. package/dist/core/tools/grep.js.map +1 -1
  95. package/dist/core/tools/ls.d.ts.map +1 -1
  96. package/dist/core/tools/ls.js +1 -1
  97. package/dist/core/tools/ls.js.map +1 -1
  98. package/dist/core/tools/read.d.ts.map +1 -1
  99. package/dist/core/tools/read.js +1 -1
  100. package/dist/core/tools/read.js.map +1 -1
  101. package/dist/core/tools/write.d.ts.map +1 -1
  102. package/dist/core/tools/write.js +1 -1
  103. package/dist/core/tools/write.js.map +1 -1
  104. package/dist/core/trust-manager.d.ts +10 -0
  105. package/dist/core/trust-manager.d.ts.map +1 -0
  106. package/dist/core/trust-manager.js +133 -0
  107. package/dist/core/trust-manager.js.map +1 -0
  108. package/dist/index.d.ts +5 -4
  109. package/dist/index.d.ts.map +1 -1
  110. package/dist/index.js +2 -1
  111. package/dist/index.js.map +1 -1
  112. package/dist/main.d.ts.map +1 -1
  113. package/dist/main.js +195 -6
  114. package/dist/main.js.map +1 -1
  115. package/dist/modes/index.d.ts +1 -1
  116. package/dist/modes/index.d.ts.map +1 -1
  117. package/dist/modes/index.js.map +1 -1
  118. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  119. package/dist/modes/interactive/components/bash-execution.js +2 -2
  120. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  121. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  122. package/dist/modes/interactive/components/footer.js +7 -0
  123. package/dist/modes/interactive/components/footer.js.map +1 -1
  124. package/dist/modes/interactive/components/index.d.ts +1 -0
  125. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  126. package/dist/modes/interactive/components/index.js +1 -0
  127. package/dist/modes/interactive/components/index.js.map +1 -1
  128. package/dist/modes/interactive/components/login-dialog.d.ts +0 -1
  129. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  130. package/dist/modes/interactive/components/login-dialog.js +3 -12
  131. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  132. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  133. package/dist/modes/interactive/components/tool-execution.js +22 -0
  134. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  135. package/dist/modes/interactive/components/trust-selector.d.ts +20 -0
  136. package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -0
  137. package/dist/modes/interactive/components/trust-selector.js +86 -0
  138. package/dist/modes/interactive/components/trust-selector.js.map +1 -0
  139. package/dist/modes/interactive/interactive-mode.d.ts +7 -0
  140. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  141. package/dist/modes/interactive/interactive-mode.js +87 -1
  142. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  143. package/dist/modes/print-mode.d.ts.map +1 -1
  144. package/dist/modes/print-mode.js +1 -0
  145. package/dist/modes/print-mode.js.map +1 -1
  146. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  147. package/dist/modes/rpc/rpc-mode.js +1 -0
  148. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  149. package/dist/package-manager-cli.d.ts.map +1 -1
  150. package/dist/package-manager-cli.js +55 -8
  151. package/dist/package-manager-cli.js.map +1 -1
  152. package/dist/utils/git.d.ts.map +1 -1
  153. package/dist/utils/git.js +54 -22
  154. package/dist/utils/git.js.map +1 -1
  155. package/dist/utils/open-browser.d.ts +9 -0
  156. package/dist/utils/open-browser.d.ts.map +1 -0
  157. package/dist/utils/open-browser.js +22 -0
  158. package/dist/utils/open-browser.js.map +1 -0
  159. package/docs/containerization.md +111 -0
  160. package/docs/docs.json +8 -0
  161. package/docs/extensions.md +58 -12
  162. package/docs/index.md +2 -0
  163. package/docs/packages.md +3 -1
  164. package/docs/prompt-templates.md +1 -1
  165. package/docs/providers.md +5 -0
  166. package/docs/rpc.md +1 -1
  167. package/docs/sdk.md +5 -0
  168. package/docs/security.md +57 -0
  169. package/docs/settings.md +10 -0
  170. package/docs/skills.md +1 -1
  171. package/docs/terminal-setup.md +36 -2
  172. package/docs/themes.md +1 -1
  173. package/docs/tmux.md +4 -2
  174. package/docs/tui.md +10 -1
  175. package/docs/usage.md +16 -4
  176. package/examples/extensions/README.md +2 -0
  177. package/examples/extensions/custom-header.ts +1 -1
  178. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  179. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  180. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  181. package/examples/extensions/doom-overlay/index.ts +1 -1
  182. package/examples/extensions/gondolin/index.ts +531 -0
  183. package/examples/extensions/gondolin/package-lock.json +185 -0
  184. package/examples/extensions/gondolin/package.json +19 -0
  185. package/examples/extensions/handoff.ts +1 -1
  186. package/examples/extensions/interactive-shell.ts +1 -1
  187. package/examples/extensions/overlay-qa-tests.ts +152 -81
  188. package/examples/extensions/project-trust.ts +64 -0
  189. package/examples/extensions/qna.ts +1 -1
  190. package/examples/extensions/question.ts +1 -1
  191. package/examples/extensions/questionnaire.ts +1 -1
  192. package/examples/extensions/sandbox/package-lock.json +2 -2
  193. package/examples/extensions/sandbox/package.json +1 -1
  194. package/examples/extensions/snake.ts +1 -1
  195. package/examples/extensions/space-invaders.ts +1 -1
  196. package/examples/extensions/summarize.ts +1 -1
  197. package/examples/extensions/tic-tac-toe.ts +1 -1
  198. package/examples/extensions/todo.ts +1 -1
  199. package/examples/extensions/tools.ts +5 -0
  200. package/examples/extensions/with-deps/package-lock.json +2 -2
  201. package/examples/extensions/with-deps/package.json +1 -1
  202. package/npm-shrinkwrap.json +12 -12
  203. package/package.json +5 -8
@@ -0,0 +1,185 @@
1
+ {
2
+ "name": "pi-extension-gondolin",
3
+ "version": "0.79.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "pi-extension-gondolin",
9
+ "version": "0.79.0",
10
+ "dependencies": {
11
+ "@earendil-works/gondolin": "0.12.0"
12
+ }
13
+ },
14
+ "node_modules/@cto.af/wtf8": {
15
+ "version": "0.0.5",
16
+ "resolved": "https://registry.npmjs.org/@cto.af/wtf8/-/wtf8-0.0.5.tgz",
17
+ "integrity": "sha512-LfUFi+Vv4eDzj+XAtR89e3wwjXA/NZjUSwU5NhwbBrLecxPaBYFy3exCuc1j+D4UZeOVdqlsl8G7LmOt18V0tg==",
18
+ "license": "MIT",
19
+ "engines": {
20
+ "node": ">=20"
21
+ }
22
+ },
23
+ "node_modules/@earendil-works/gondolin": {
24
+ "version": "0.12.0",
25
+ "resolved": "https://registry.npmjs.org/@earendil-works/gondolin/-/gondolin-0.12.0.tgz",
26
+ "integrity": "sha512-BXbvzQKb5QmxY5NtthRDONJTu7+IDKbzqWGrJyyNXMP7N681Tx0Q9TK8pK1ba8nUvYQTipNJyGZOsJfYiZll1A==",
27
+ "license": "Apache-2.0",
28
+ "dependencies": {
29
+ "cbor2": "^2.3.0",
30
+ "node-forge": "^1.3.3",
31
+ "ssh2": "^1.17.0",
32
+ "undici": "^6.21.0"
33
+ },
34
+ "bin": {
35
+ "gondolin": "dist/bin/gondolin.js"
36
+ },
37
+ "engines": {
38
+ "node": ">=23.6.0"
39
+ },
40
+ "optionalDependencies": {
41
+ "@earendil-works/gondolin-krun-runner-darwin-arm64": "0.12.0",
42
+ "@earendil-works/gondolin-krun-runner-linux-x64": "0.12.0"
43
+ }
44
+ },
45
+ "node_modules/@earendil-works/gondolin-krun-runner-darwin-arm64": {
46
+ "version": "0.12.0",
47
+ "resolved": "https://registry.npmjs.org/@earendil-works/gondolin-krun-runner-darwin-arm64/-/gondolin-krun-runner-darwin-arm64-0.12.0.tgz",
48
+ "integrity": "sha512-ftDlusht4PcT7Y3TuPrZIKrCXy3isiBTVMvlXYK0pcud2uXY6uwFTGeunYgP+8ND/60ddb+MImqbfmkcK8B84A==",
49
+ "cpu": [
50
+ "arm64"
51
+ ],
52
+ "license": "Apache-2.0",
53
+ "optional": true,
54
+ "os": [
55
+ "darwin"
56
+ ],
57
+ "bin": {
58
+ "gondolin-krun-runner": "bin/gondolin-krun-runner"
59
+ }
60
+ },
61
+ "node_modules/@earendil-works/gondolin-krun-runner-linux-x64": {
62
+ "version": "0.12.0",
63
+ "resolved": "https://registry.npmjs.org/@earendil-works/gondolin-krun-runner-linux-x64/-/gondolin-krun-runner-linux-x64-0.12.0.tgz",
64
+ "integrity": "sha512-RRYsgwe2r5ApKmFNy469QgwnyjAHpAs9XANdWpTd9ol4iUYOY3sX7e0xIooAKxd+ktxGI4N/xRWicwGen3D/Ow==",
65
+ "cpu": [
66
+ "x64"
67
+ ],
68
+ "license": "Apache-2.0",
69
+ "optional": true,
70
+ "os": [
71
+ "linux"
72
+ ],
73
+ "bin": {
74
+ "gondolin-krun-runner": "bin/gondolin-krun-runner"
75
+ }
76
+ },
77
+ "node_modules/asn1": {
78
+ "version": "0.2.6",
79
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
80
+ "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
81
+ "license": "MIT",
82
+ "dependencies": {
83
+ "safer-buffer": "~2.1.0"
84
+ }
85
+ },
86
+ "node_modules/bcrypt-pbkdf": {
87
+ "version": "1.0.2",
88
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
89
+ "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
90
+ "license": "BSD-3-Clause",
91
+ "dependencies": {
92
+ "tweetnacl": "^0.14.3"
93
+ }
94
+ },
95
+ "node_modules/buildcheck": {
96
+ "version": "0.0.7",
97
+ "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.7.tgz",
98
+ "integrity": "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==",
99
+ "optional": true,
100
+ "engines": {
101
+ "node": ">=10.0.0"
102
+ }
103
+ },
104
+ "node_modules/cbor2": {
105
+ "version": "2.3.0",
106
+ "resolved": "https://registry.npmjs.org/cbor2/-/cbor2-2.3.0.tgz",
107
+ "integrity": "sha512-76WB3hq8BoaGkMkBVJ27fW5LJU+qqDLEpgRNCG/SYKhODWXpVPOTD4UcUto3IEzYLA52nsvbhb0wabhHDn3qXg==",
108
+ "license": "MIT",
109
+ "dependencies": {
110
+ "@cto.af/wtf8": "0.0.5"
111
+ },
112
+ "engines": {
113
+ "node": ">=20"
114
+ }
115
+ },
116
+ "node_modules/cpu-features": {
117
+ "version": "0.0.10",
118
+ "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz",
119
+ "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==",
120
+ "hasInstallScript": true,
121
+ "optional": true,
122
+ "dependencies": {
123
+ "buildcheck": "~0.0.6",
124
+ "nan": "^2.19.0"
125
+ },
126
+ "engines": {
127
+ "node": ">=10.0.0"
128
+ }
129
+ },
130
+ "node_modules/nan": {
131
+ "version": "2.27.0",
132
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.27.0.tgz",
133
+ "integrity": "sha512-hC+0LidcL3XE4rp1C4H54KujgXKzbfyTngZTwBByQxsOxCEKZT0MPQ4hOKUH2jU1OYstqdDH4onyHPDzcV0XdQ==",
134
+ "license": "MIT",
135
+ "optional": true
136
+ },
137
+ "node_modules/node-forge": {
138
+ "version": "1.4.0",
139
+ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz",
140
+ "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==",
141
+ "license": "(BSD-3-Clause OR GPL-2.0)",
142
+ "engines": {
143
+ "node": ">= 6.13.0"
144
+ }
145
+ },
146
+ "node_modules/safer-buffer": {
147
+ "version": "2.1.2",
148
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
149
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
150
+ "license": "MIT"
151
+ },
152
+ "node_modules/ssh2": {
153
+ "version": "1.17.0",
154
+ "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz",
155
+ "integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==",
156
+ "hasInstallScript": true,
157
+ "dependencies": {
158
+ "asn1": "^0.2.6",
159
+ "bcrypt-pbkdf": "^1.0.2"
160
+ },
161
+ "engines": {
162
+ "node": ">=10.16.0"
163
+ },
164
+ "optionalDependencies": {
165
+ "cpu-features": "~0.0.10",
166
+ "nan": "^2.23.0"
167
+ }
168
+ },
169
+ "node_modules/tweetnacl": {
170
+ "version": "0.14.5",
171
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
172
+ "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
173
+ "license": "Unlicense"
174
+ },
175
+ "node_modules/undici": {
176
+ "version": "6.26.0",
177
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz",
178
+ "integrity": "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==",
179
+ "license": "MIT",
180
+ "engines": {
181
+ "node": ">=18.17"
182
+ }
183
+ }
184
+ }
185
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "pi-extension-gondolin",
3
+ "private": true,
4
+ "version": "0.79.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "clean": "echo 'nothing to clean'",
8
+ "build": "echo 'nothing to build'",
9
+ "check": "echo 'nothing to check'"
10
+ },
11
+ "pi": {
12
+ "extensions": [
13
+ "./index.ts"
14
+ ]
15
+ },
16
+ "dependencies": {
17
+ "@earendil-works/gondolin": "0.12.0"
18
+ }
19
+ }
@@ -81,7 +81,7 @@ export default function (pi: ExtensionAPI) {
81
81
  pi.registerCommand("handoff", {
82
82
  description: "Transfer context to a new focused session",
83
83
  handler: async (args, ctx) => {
84
- if (!ctx.hasUI) {
84
+ if (ctx.mode !== "tui") {
85
85
  ctx.ui.notify("handoff requires interactive mode", "error");
86
86
  return;
87
87
  }
@@ -146,7 +146,7 @@ export default function (pi: ExtensionAPI) {
146
146
  }
147
147
 
148
148
  // No UI available (print mode, RPC, etc.)
149
- if (!ctx.hasUI) {
149
+ if (ctx.mode !== "tui") {
150
150
  return {
151
151
  result: { output: "(interactive commands require TUI)", exitCode: 1, cancelled: false, truncated: false },
152
152
  };
@@ -15,13 +15,13 @@
15
15
  * /overlay-sidepanel - Responsive sidepanel (hides when terminal < 100 cols)
16
16
  * /overlay-toggle - Toggle visibility demo (demonstrates OverlayHandle.setHidden)
17
17
  * /overlay-passive - Non-capturing overlay demo (passive info panel alongside active overlay)
18
- * /overlay-focus - Focus cycling and rendering order with non-capturing overlays
18
+ * /overlay-focus - Focus cycling, input routing, dismissal, and rendering order with overlays
19
19
  * /overlay-streaming - Multiple input panels with simulated streaming (Tab to cycle focus)
20
20
  */
21
21
 
22
22
  import type { ExtensionAPI, ExtensionCommandContext, Theme } from "@earendil-works/pi-coding-agent";
23
23
  import type { Component, OverlayAnchor, OverlayHandle, OverlayOptions, TUI } from "@earendil-works/pi-tui";
24
- import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
24
+ import { Input, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
25
25
  import { spawn } from "child_process";
26
26
 
27
27
  // Global handle for toggle demo (in real code, use a more elegant pattern)
@@ -272,9 +272,9 @@ export default function (pi: ExtensionAPI) {
272
272
  },
273
273
  });
274
274
 
275
- // Focus cycling demo - demonstrates focus(), unfocus(), isFocused() and rendering order
275
+ // Focus cycling demo - demonstrates focus(), input routing, per-panel dismissal, and rendering order
276
276
  pi.registerCommand("overlay-focus", {
277
- description: "Test focus cycling and rendering order with non-capturing overlays",
277
+ description: "Test focus cycling, input routing, dismissal, and rendering order with overlays",
278
278
  handler: async (_args: string, ctx: ExtensionCommandContext) => {
279
279
  ctx.ui.setEditorText("");
280
280
  await ctx.ui.custom<void>((tui, theme, _kb, done) => new FocusDemoController(tui, theme, done), {
@@ -1031,57 +1031,66 @@ class TimerPanel extends BaseOverlay {
1031
1031
 
1032
1032
  // === Focus cycling demo ===
1033
1033
 
1034
+ type FocusPanelColor = "error" | "success" | "accent";
1035
+ type FocusPanelConfig = { label: string; color: FocusPanelColor; options: OverlayOptions };
1036
+ type FocusPanelEntry = { panel: FocusPanel; handle: OverlayHandle };
1037
+
1038
+ const FOCUS_PANEL_CONFIGS = [
1039
+ { label: "Alpha", color: "error", options: { row: 2, col: 4, width: 34 } },
1040
+ { label: "Beta", color: "success", options: { row: 5, col: 28, width: 34 } },
1041
+ { label: "Gamma", color: "accent", options: { row: 8, col: 52, width: 34 } },
1042
+ ] satisfies FocusPanelConfig[];
1043
+
1034
1044
  class FocusDemoController extends BaseOverlay {
1035
- private tui: TUI;
1036
- private panels: FocusPanel[] = [];
1037
- private handles: OverlayHandle[] = [];
1038
- private focusIndex = -1;
1039
- private done: () => void;
1045
+ private readonly tui: TUI;
1046
+ private entries: FocusPanelEntry[] = [];
1047
+ private readonly done: () => void;
1048
+ private closed = false;
1040
1049
 
1041
1050
  constructor(tui: TUI, theme: Theme, done: () => void) {
1042
1051
  super(theme);
1043
1052
  this.tui = tui;
1044
1053
  this.done = done;
1045
- const colors = ["error", "success", "accent"] as const;
1046
- const labels = ["Alpha", "Beta", "Gamma"];
1047
1054
 
1048
- for (let i = 0; i < 3; i++) {
1049
- const panel = new FocusPanel(
1050
- theme,
1051
- labels[i]!,
1052
- colors[i]!,
1053
- () => this.cycleFocus(),
1054
- () => this.close(),
1055
- );
1056
- const handle = this.tui.showOverlay(panel, {
1057
- nonCapturing: true,
1058
- row: 2,
1059
- col: 5 + i * 6,
1060
- width: 28,
1061
- });
1062
- panel.handle = handle;
1063
- this.panels.push(panel);
1064
- this.handles.push(handle);
1055
+ for (const config of FOCUS_PANEL_CONFIGS) {
1056
+ const panel = new FocusPanel({ theme, config, controller: this });
1057
+ const handle = this.tui.showOverlay(panel, { nonCapturing: true, ...config.options });
1058
+ this.entries.push({ panel, handle });
1065
1059
  }
1060
+
1061
+ this.focusFirstOpenPanel();
1066
1062
  }
1067
1063
 
1068
- private cycleFocus(): void {
1069
- if (this.focusIndex >= 0 && this.focusIndex < this.handles.length) {
1070
- this.handles[this.focusIndex]!.unfocus();
1071
- }
1072
- this.focusIndex++;
1073
- if (this.focusIndex >= this.handles.length) {
1074
- this.focusIndex = -1;
1075
- } else {
1076
- this.handles[this.focusIndex]!.focus();
1064
+ focusNext(current: FocusPanel, direction: 1 | -1 = 1): void {
1065
+ const openEntries = this.openEntries();
1066
+ const currentOpenPosition = openEntries.findIndex((entry) => entry.panel === current);
1067
+ if (currentOpenPosition === -1) throw new Error(`Panel ${current.label} is not open`);
1068
+ const nextOpenPosition = (currentOpenPosition + direction + openEntries.length) % openEntries.length;
1069
+ this.focusEntryAt(openEntries, nextOpenPosition);
1070
+ }
1071
+
1072
+ dismiss(panel: FocusPanel): void {
1073
+ const openEntries = this.openEntries();
1074
+ const currentOpenPosition = openEntries.findIndex((candidate) => candidate.panel === panel);
1075
+ if (currentOpenPosition === -1) return;
1076
+ const entry = openEntries[currentOpenPosition];
1077
+ if (!entry) throw new Error(`Invalid focus panel index ${currentOpenPosition}`);
1078
+ const remainingEntries = openEntries.filter((candidate) => candidate.panel !== panel);
1079
+
1080
+ entry.panel.closed = true;
1081
+ entry.handle.hide();
1082
+ if (remainingEntries.length === 0) {
1083
+ this.close();
1084
+ return;
1077
1085
  }
1078
- this.tui.requestRender();
1086
+
1087
+ this.focusEntryAt(remainingEntries, currentOpenPosition % remainingEntries.length);
1079
1088
  }
1080
1089
 
1081
- private close(): void {
1082
- for (const handle of this.handles) handle.hide();
1083
- this.handles = [];
1084
- this.panels = [];
1090
+ close(): void {
1091
+ if (this.closed) return;
1092
+ this.closed = true;
1093
+ this.hidePanels();
1085
1094
  this.done();
1086
1095
  }
1087
1096
 
@@ -1089,86 +1098,148 @@ class FocusDemoController extends BaseOverlay {
1089
1098
  if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
1090
1099
  this.close();
1091
1100
  } else if (matchesKey(data, "tab")) {
1092
- this.cycleFocus();
1101
+ this.focusFirstOpenPanel();
1093
1102
  }
1094
1103
  }
1095
1104
 
1096
1105
  render(width: number): string[] {
1097
1106
  const th = this.theme;
1098
- const focused = this.focusIndex === -1 ? "Controller" : (this.panels[this.focusIndex]?.label ?? "?");
1107
+ const focused = this.entries.find((entry) => entry.handle.isFocused())?.panel.label ?? "Controller";
1099
1108
  return this.box(
1100
1109
  [
1101
1110
  "",
1102
1111
  ` Current focus: ${th.fg("accent", focused)}`,
1103
1112
  "",
1104
1113
  " Three overlapping panels above are",
1105
- ` all ${th.fg("accent", "nonCapturing")}. Press Tab to`,
1106
- " cycle focus() between them.",
1114
+ ` ${th.fg("accent", "nonCapturing")} overlays controlled with`,
1115
+ " raw OverlayHandle.focus()/hide().",
1107
1116
  "",
1108
- " Focused panel renders on top",
1109
- " (focus-based rendering order).",
1117
+ " Type in the focused panel's input.",
1118
+ " Focused panel renders on top.",
1110
1119
  "",
1111
- th.fg("dim", " Tab = cycle focus | Esc = close"),
1120
+ th.fg("dim", " Tab/Shift+Tab = cycle panels"),
1121
+ th.fg("dim", " Esc/Ctrl+D = dismiss panel"),
1122
+ th.fg("dim", " Ctrl+C = close all"),
1112
1123
  "",
1113
1124
  ],
1114
1125
  width,
1115
- "Focus Demo",
1126
+ "Focus + Input Demo",
1116
1127
  );
1117
1128
  }
1118
1129
 
1119
1130
  override dispose(): void {
1120
- for (const handle of this.handles) handle.hide();
1131
+ if (this.closed) return;
1132
+ this.closed = true;
1133
+ this.hidePanels();
1134
+ }
1135
+
1136
+ private focusFirstOpenPanel(): void {
1137
+ const firstOpen = this.openEntries()[0];
1138
+ if (firstOpen) {
1139
+ firstOpen.handle.focus();
1140
+ this.tui.requestRender();
1141
+ }
1142
+ }
1143
+
1144
+ private focusEntryAt(entries: FocusPanelEntry[], index: number): void {
1145
+ const entry = entries[index];
1146
+ if (!entry) throw new Error(`Invalid focus panel index ${index}`);
1147
+ entry.handle.focus();
1148
+ this.tui.requestRender();
1149
+ }
1150
+
1151
+ private hidePanels(): void {
1152
+ for (const entry of this.entries) {
1153
+ if (!entry.panel.closed) {
1154
+ entry.panel.closed = true;
1155
+ entry.handle.hide();
1156
+ }
1157
+ }
1158
+ this.entries = [];
1159
+ }
1160
+
1161
+ private openEntries(): FocusPanelEntry[] {
1162
+ return this.entries.filter((entry) => !entry.panel.closed);
1121
1163
  }
1122
1164
  }
1123
1165
 
1124
1166
  class FocusPanel extends BaseOverlay {
1125
- handle: OverlayHandle | null = null;
1167
+ focused = false;
1168
+ closed = false;
1126
1169
  readonly label: string;
1127
- private color: "error" | "success" | "accent";
1128
- private onTab: () => void;
1129
- private onClose: () => void;
1130
-
1131
- constructor(
1132
- theme: Theme,
1133
- label: string,
1134
- color: "error" | "success" | "accent",
1135
- onTab: () => void,
1136
- onClose: () => void,
1137
- ) {
1170
+ private readonly color: FocusPanelColor;
1171
+ private readonly controller: FocusDemoController;
1172
+ private readonly input = new Input();
1173
+ private inputs: string[] = [];
1174
+
1175
+ constructor({
1176
+ theme,
1177
+ config,
1178
+ controller,
1179
+ }: {
1180
+ theme: Theme;
1181
+ config: FocusPanelConfig;
1182
+ controller: FocusDemoController;
1183
+ }) {
1138
1184
  super(theme);
1139
- this.label = label;
1140
- this.color = color;
1141
- this.onTab = onTab;
1142
- this.onClose = onClose;
1185
+ this.label = config.label;
1186
+ this.color = config.color;
1187
+ this.controller = controller;
1143
1188
  }
1144
1189
 
1145
1190
  handleInput(data: string): void {
1146
1191
  if (matchesKey(data, "tab")) {
1147
- this.onTab();
1148
- } else if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
1149
- this.onClose();
1192
+ this.controller.focusNext(this);
1193
+ } else if (matchesKey(data, "shift+tab")) {
1194
+ this.controller.focusNext(this, -1);
1195
+ } else if (matchesKey(data, "escape") || matchesKey(data, "ctrl+d")) {
1196
+ this.controller.dismiss(this);
1197
+ } else if (matchesKey(data, "ctrl+c")) {
1198
+ this.controller.close();
1199
+ } else if (matchesKey(data, "return")) {
1200
+ this.inputs.push("Enter");
1201
+ } else if (matchesKey(data, "up")) {
1202
+ this.inputs.push("↑");
1203
+ } else if (matchesKey(data, "down")) {
1204
+ this.inputs.push("↓");
1205
+ } else if (matchesKey(data, "left")) {
1206
+ this.input.handleInput(data);
1207
+ this.inputs.push("←");
1208
+ } else if (matchesKey(data, "right")) {
1209
+ this.input.handleInput(data);
1210
+ this.inputs.push("→");
1211
+ } else if (matchesKey(data, "backspace")) {
1212
+ this.input.handleInput(data);
1213
+ this.inputs.push("Backspace");
1214
+ } else {
1215
+ this.input.handleInput(data);
1216
+ this.inputs.push(JSON.stringify(data));
1150
1217
  }
1151
1218
  }
1152
1219
 
1153
1220
  render(width: number): string[] {
1154
1221
  const th = this.theme;
1155
- const focused = this.handle?.isFocused() ?? false;
1156
1222
  const innerW = Math.max(1, width - 2);
1157
- const border = (c: string) => th.fg(this.color, c);
1223
+ const border = (c: string) => th.fg(this.focused ? this.color : "dim", c);
1158
1224
  const padLine = (s: string) => truncateToWidth(s, innerW, "...", true);
1225
+ const recent = this.inputs.length === 0 ? "(none)" : this.inputs.slice(-6).join(" ");
1159
1226
  const lines: string[] = [];
1160
1227
 
1228
+ this.input.focused = this.focused;
1229
+ const [inputLine = ""] = this.input.render(Math.max(1, innerW - 8));
1161
1230
  lines.push(border(`╭${"─".repeat(innerW)}╮`));
1162
- lines.push(border("│") + padLine(` ${th.fg("accent", this.label)}`) + border("│"));
1163
- lines.push(border("│") + padLine("") + border("│"));
1164
- if (focused) {
1165
- lines.push(border("│") + padLine(th.fg("success", "FOCUSED")) + border(""));
1166
- lines.push(border("│") + padLine(th.fg("dim", " (receiving input)")) + border("│"));
1167
- } else {
1168
- lines.push(border("│") + padLine(th.fg("dim", " ○ unfocused")) + border("│"));
1169
- lines.push(border("│") + padLine(th.fg("dim", " (passive)")) + border("│"));
1170
- }
1231
+ lines.push(
1232
+ border("│") +
1233
+ padLine(
1234
+ ` ${th.fg(this.color, this.label)} ${this.focused ? th.fg("success", "FOCUSED") : th.fg("dim", "visible")}`,
1235
+ ) +
1236
+ border("│"),
1237
+ );
1171
1238
  lines.push(border("│") + padLine("") + border("│"));
1239
+ lines.push(border("│") + padLine(` Input: ${inputLine}`) + border("│"));
1240
+ lines.push(border("│") + padLine(` Keys: ${recent}`) + border("│"));
1241
+ lines.push(border("│") + padLine(th.fg("dim", " Tab/Shift+Tab focus")) + border("│"));
1242
+ lines.push(border("│") + padLine(th.fg("dim", " Esc/Ctrl+D dismiss")) + border("│"));
1172
1243
  lines.push(border(`╰${"─".repeat(innerW)}╯`));
1173
1244
 
1174
1245
  return lines;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Project Trust Extension
3
+ *
4
+ * Demonstrates the project_trust event. Install globally or pass via -e:
5
+ *
6
+ * mkdir -p ~/.pi/agent/extensions
7
+ * cp packages/coding-agent/examples/extensions/project-trust.ts ~/.pi/agent/extensions/
8
+ *
9
+ * Or:
10
+ *
11
+ * pi -e packages/coding-agent/examples/extensions/project-trust.ts
12
+ *
13
+ * Try it in a project containing .pi, AGENTS.md/CLAUDE.md, or .agents/skills.
14
+ */
15
+
16
+ import type { ExtensionAPI, ProjectTrustEventResult } from "@earendil-works/pi-coding-agent";
17
+
18
+ export default function (pi: ExtensionAPI) {
19
+ let loadCount = 0;
20
+ loadCount++;
21
+
22
+ // Multiple handlers in one extension are allowed. The first handler that returns
23
+ // { trusted: "yes" } or { trusted: "no" } wins and suppresses the built-in
24
+ // trust prompt. Return { trusted: "undecided" } to let another handler or the
25
+ // built-in flow decide.
26
+ pi.on("project_trust", async (event, ctx): Promise<ProjectTrustEventResult> => {
27
+ ctx.ui.notify(`project_trust fired for ${event.cwd} (mode: ${ctx.mode}, load: ${loadCount})`, "info");
28
+
29
+ if (!ctx.hasUI) {
30
+ return { trusted: "undecided" };
31
+ }
32
+
33
+ const choice = await ctx.ui.select(`Project trust for:\n${event.cwd}`, [
34
+ "Trust and remember",
35
+ "Trust with note and remember",
36
+ "Trust this session",
37
+ "Do not trust this session",
38
+ "Let built-in prompt decide",
39
+ ]);
40
+
41
+ if (choice === "Trust with note and remember") {
42
+ const note = await ctx.ui.input("Project trust note", "Optional note for this demo");
43
+ ctx.ui.notify(note ? `Recorded demo note: ${note}` : "No demo note entered", "info");
44
+ return { trusted: "yes", remember: true };
45
+ }
46
+ if (choice === "Trust and remember") {
47
+ return { trusted: "yes", remember: true };
48
+ }
49
+ if (choice === "Trust this session") {
50
+ return { trusted: "yes" };
51
+ }
52
+ if (choice === "Do not trust this session") {
53
+ return { trusted: "no" };
54
+ }
55
+ if (choice === "Let built-in prompt decide") {
56
+ return { trusted: "undecided" };
57
+ }
58
+ return { trusted: "undecided" };
59
+ });
60
+
61
+ pi.on("session_start", (_event, ctx) => {
62
+ ctx.ui.notify(`project-trust example loaded after trust resolution in ${ctx.cwd}`, "info");
63
+ });
64
+ }
@@ -31,7 +31,7 @@ export default function (pi: ExtensionAPI) {
31
31
  pi.registerCommand("qna", {
32
32
  description: "Extract questions from last assistant message into editor",
33
33
  handler: async (_args, ctx) => {
34
- if (!ctx.hasUI) {
34
+ if (ctx.mode !== "tui") {
35
35
  ctx.ui.notify("qna requires interactive mode", "error");
36
36
  return;
37
37
  }
@@ -41,7 +41,7 @@ export default function question(pi: ExtensionAPI) {
41
41
  parameters: QuestionParams,
42
42
 
43
43
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
44
- if (!ctx.hasUI) {
44
+ if (ctx.mode !== "tui") {
45
45
  return {
46
46
  content: [{ type: "text", text: "Error: UI not available (running in non-interactive mode)" }],
47
47
  details: {
@@ -82,7 +82,7 @@ export default function questionnaire(pi: ExtensionAPI) {
82
82
  parameters: QuestionnaireParams,
83
83
 
84
84
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
85
- if (!ctx.hasUI) {
85
+ if (ctx.mode !== "tui") {
86
86
  return errorResult("Error: UI not available (running in non-interactive mode)");
87
87
  }
88
88
  if (params.questions.length === 0) {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-sandbox",
9
- "version": "1.8.0",
9
+ "version": "1.9.0",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sandbox-runtime": "^0.0.26"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
3
  "private": true,
4
- "version": "1.8.0",
4
+ "version": "1.9.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",