pi-ui-extend 0.1.1

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 (420) hide show
  1. package/README.md +307 -0
  2. package/bin/pix.mjs +219 -0
  3. package/dist/app/app.d.ts +96 -0
  4. package/dist/app/app.js +871 -0
  5. package/dist/app/blink-controller.d.ts +23 -0
  6. package/dist/app/blink-controller.js +82 -0
  7. package/dist/app/cli.d.ts +8 -0
  8. package/dist/app/cli.js +83 -0
  9. package/dist/app/clipboard.d.ts +1 -0
  10. package/dist/app/clipboard.js +24 -0
  11. package/dist/app/command-controller.d.ts +18 -0
  12. package/dist/app/command-controller.js +58 -0
  13. package/dist/app/command-host.d.ts +44 -0
  14. package/dist/app/command-host.js +1 -0
  15. package/dist/app/command-model-actions.d.ts +12 -0
  16. package/dist/app/command-model-actions.js +176 -0
  17. package/dist/app/command-navigation-actions.d.ts +19 -0
  18. package/dist/app/command-navigation-actions.js +267 -0
  19. package/dist/app/command-registry.d.ts +32 -0
  20. package/dist/app/command-registry.js +225 -0
  21. package/dist/app/command-runtime.d.ts +5 -0
  22. package/dist/app/command-runtime.js +32 -0
  23. package/dist/app/command-session-actions.d.ts +20 -0
  24. package/dist/app/command-session-actions.js +295 -0
  25. package/dist/app/constants.d.ts +52 -0
  26. package/dist/app/constants.js +103 -0
  27. package/dist/app/conversation-entry-renderer.d.ts +21 -0
  28. package/dist/app/conversation-entry-renderer.js +81 -0
  29. package/dist/app/conversation-shell-renderer.d.ts +5 -0
  30. package/dist/app/conversation-shell-renderer.js +43 -0
  31. package/dist/app/conversation-tool-renderer.d.ts +16 -0
  32. package/dist/app/conversation-tool-renderer.js +216 -0
  33. package/dist/app/conversation-viewport.d.ts +55 -0
  34. package/dist/app/conversation-viewport.js +252 -0
  35. package/dist/app/dcp-stats.d.ts +2 -0
  36. package/dist/app/dcp-stats.js +116 -0
  37. package/dist/app/editor-layout-renderer.d.ts +31 -0
  38. package/dist/app/editor-layout-renderer.js +211 -0
  39. package/dist/app/editor-panels.d.ts +4 -0
  40. package/dist/app/editor-panels.js +130 -0
  41. package/dist/app/extension-actions-controller.d.ts +22 -0
  42. package/dist/app/extension-actions-controller.js +60 -0
  43. package/dist/app/extension-event-bus.d.ts +3 -0
  44. package/dist/app/extension-event-bus.js +23 -0
  45. package/dist/app/extension-ui-controller.d.ts +57 -0
  46. package/dist/app/extension-ui-controller.js +532 -0
  47. package/dist/app/file-link-opener.d.ts +2 -0
  48. package/dist/app/file-link-opener.js +66 -0
  49. package/dist/app/file-links.d.ts +10 -0
  50. package/dist/app/file-links.js +117 -0
  51. package/dist/app/guards.d.ts +3 -0
  52. package/dist/app/guards.js +9 -0
  53. package/dist/app/icons.d.ts +37 -0
  54. package/dist/app/icons.js +97 -0
  55. package/dist/app/id.d.ts +1 -0
  56. package/dist/app/id.js +4 -0
  57. package/dist/app/image-click-targets.d.ts +5 -0
  58. package/dist/app/image-click-targets.js +32 -0
  59. package/dist/app/image-opener.d.ts +2 -0
  60. package/dist/app/image-opener.js +64 -0
  61. package/dist/app/input-action-controller.d.ts +47 -0
  62. package/dist/app/input-action-controller.js +209 -0
  63. package/dist/app/input-controller.d.ts +60 -0
  64. package/dist/app/input-controller.js +425 -0
  65. package/dist/app/input-paste-handler.d.ts +27 -0
  66. package/dist/app/input-paste-handler.js +146 -0
  67. package/dist/app/menu-items-controller.d.ts +32 -0
  68. package/dist/app/menu-items-controller.js +182 -0
  69. package/dist/app/message-content.d.ts +8 -0
  70. package/dist/app/message-content.js +115 -0
  71. package/dist/app/model-ref.d.ts +13 -0
  72. package/dist/app/model-ref.js +50 -0
  73. package/dist/app/model-usage-controller.d.ts +35 -0
  74. package/dist/app/model-usage-controller.js +99 -0
  75. package/dist/app/model-usage-status.d.ts +125 -0
  76. package/dist/app/model-usage-status.js +749 -0
  77. package/dist/app/mouse-controller.d.ts +182 -0
  78. package/dist/app/mouse-controller.js +968 -0
  79. package/dist/app/native-modifiers.d.ts +3 -0
  80. package/dist/app/native-modifiers.js +60 -0
  81. package/dist/app/nerd-font-controller.d.ts +11 -0
  82. package/dist/app/nerd-font-controller.js +90 -0
  83. package/dist/app/popup-action-controller.d.ts +44 -0
  84. package/dist/app/popup-action-controller.js +278 -0
  85. package/dist/app/popup-menu-controller.d.ts +183 -0
  86. package/dist/app/popup-menu-controller.js +1070 -0
  87. package/dist/app/prompt-enhancer-controller.d.ts +40 -0
  88. package/dist/app/prompt-enhancer-controller.js +215 -0
  89. package/dist/app/queued-message-controller.d.ts +62 -0
  90. package/dist/app/queued-message-controller.js +377 -0
  91. package/dist/app/render-controller.d.ts +41 -0
  92. package/dist/app/render-controller.js +378 -0
  93. package/dist/app/render-text.d.ts +19 -0
  94. package/dist/app/render-text.js +67 -0
  95. package/dist/app/request-history.d.ts +23 -0
  96. package/dist/app/request-history.js +131 -0
  97. package/dist/app/runtime.d.ts +39 -0
  98. package/dist/app/runtime.js +195 -0
  99. package/dist/app/screen-selection.d.ts +6 -0
  100. package/dist/app/screen-selection.js +9 -0
  101. package/dist/app/screen-styler.d.ts +34 -0
  102. package/dist/app/screen-styler.js +168 -0
  103. package/dist/app/scroll-controller.d.ts +51 -0
  104. package/dist/app/scroll-controller.js +207 -0
  105. package/dist/app/session-event-controller.d.ts +69 -0
  106. package/dist/app/session-event-controller.js +338 -0
  107. package/dist/app/session-history.d.ts +23 -0
  108. package/dist/app/session-history.js +164 -0
  109. package/dist/app/session-lifecycle-controller.d.ts +55 -0
  110. package/dist/app/session-lifecycle-controller.js +116 -0
  111. package/dist/app/session-search.d.ts +24 -0
  112. package/dist/app/session-search.js +215 -0
  113. package/dist/app/shell-command.d.ts +27 -0
  114. package/dist/app/shell-command.js +176 -0
  115. package/dist/app/shell-controller.d.ts +28 -0
  116. package/dist/app/shell-controller.js +124 -0
  117. package/dist/app/slash-commands.d.ts +6 -0
  118. package/dist/app/slash-commands.js +75 -0
  119. package/dist/app/startup-checks.d.ts +8 -0
  120. package/dist/app/startup-checks.js +59 -0
  121. package/dist/app/startup-info.d.ts +3 -0
  122. package/dist/app/startup-info.js +176 -0
  123. package/dist/app/status-controller.d.ts +35 -0
  124. package/dist/app/status-controller.js +105 -0
  125. package/dist/app/status-line-renderer.d.ts +68 -0
  126. package/dist/app/status-line-renderer.js +453 -0
  127. package/dist/app/subagents-files.d.ts +10 -0
  128. package/dist/app/subagents-files.js +193 -0
  129. package/dist/app/subagents-model.d.ts +23 -0
  130. package/dist/app/subagents-model.js +224 -0
  131. package/dist/app/subagents-widget-controller.d.ts +43 -0
  132. package/dist/app/subagents-widget-controller.js +311 -0
  133. package/dist/app/tab-line-renderer.d.ts +26 -0
  134. package/dist/app/tab-line-renderer.js +222 -0
  135. package/dist/app/tabs-controller.d.ts +100 -0
  136. package/dist/app/tabs-controller.js +885 -0
  137. package/dist/app/terminal-controller.d.ts +40 -0
  138. package/dist/app/terminal-controller.js +135 -0
  139. package/dist/app/terminal-edit-shortcuts.d.ts +23 -0
  140. package/dist/app/terminal-edit-shortcuts.js +138 -0
  141. package/dist/app/terminal-output-buffer.d.ts +20 -0
  142. package/dist/app/terminal-output-buffer.js +52 -0
  143. package/dist/app/toast-controller.d.ts +13 -0
  144. package/dist/app/toast-controller.js +40 -0
  145. package/dist/app/toast-renderer.d.ts +9 -0
  146. package/dist/app/toast-renderer.js +79 -0
  147. package/dist/app/todo-model.d.ts +22 -0
  148. package/dist/app/todo-model.js +179 -0
  149. package/dist/app/todo-widget-controller.d.ts +21 -0
  150. package/dist/app/todo-widget-controller.js +59 -0
  151. package/dist/app/tool-block-renderer.d.ts +26 -0
  152. package/dist/app/tool-block-renderer.js +439 -0
  153. package/dist/app/types.d.ts +550 -0
  154. package/dist/app/types.js +1 -0
  155. package/dist/app/update.d.ts +36 -0
  156. package/dist/app/update.js +315 -0
  157. package/dist/app/voice-controller.d.ts +52 -0
  158. package/dist/app/voice-controller.js +600 -0
  159. package/dist/app/workspace-actions-controller.d.ts +40 -0
  160. package/dist/app/workspace-actions-controller.js +215 -0
  161. package/dist/app/workspace-undo.d.ts +44 -0
  162. package/dist/app/workspace-undo.js +215 -0
  163. package/dist/config.d.ts +62 -0
  164. package/dist/config.js +527 -0
  165. package/dist/context-progress-bar.d.ts +17 -0
  166. package/dist/context-progress-bar.js +48 -0
  167. package/dist/default-pix-config.d.ts +1 -0
  168. package/dist/default-pix-config.js +375 -0
  169. package/dist/fuzzy.d.ts +23 -0
  170. package/dist/fuzzy.js +165 -0
  171. package/dist/input-editor-files.d.ts +15 -0
  172. package/dist/input-editor-files.js +62 -0
  173. package/dist/input-editor.d.ts +186 -0
  174. package/dist/input-editor.js +835 -0
  175. package/dist/main.d.ts +1 -0
  176. package/dist/main.js +12 -0
  177. package/dist/markdown-format.d.ts +22 -0
  178. package/dist/markdown-format.js +542 -0
  179. package/dist/sdk.d.ts +3 -0
  180. package/dist/sdk.js +1 -0
  181. package/dist/syntax-highlight.d.ts +22 -0
  182. package/dist/syntax-highlight.js +528 -0
  183. package/dist/terminal-width.d.ts +6 -0
  184. package/dist/terminal-width.js +245 -0
  185. package/dist/theme.d.ts +56 -0
  186. package/dist/theme.js +118 -0
  187. package/dist/tool-renderers/apply-patch.d.ts +2 -0
  188. package/dist/tool-renderers/apply-patch.js +36 -0
  189. package/dist/tool-renderers/ast.d.ts +2 -0
  190. package/dist/tool-renderers/ast.js +5 -0
  191. package/dist/tool-renderers/compress.d.ts +2 -0
  192. package/dist/tool-renderers/compress.js +126 -0
  193. package/dist/tool-renderers/index.d.ts +3 -0
  194. package/dist/tool-renderers/index.js +44 -0
  195. package/dist/tool-renderers/question.d.ts +2 -0
  196. package/dist/tool-renderers/question.js +88 -0
  197. package/dist/tool-renderers/read.d.ts +2 -0
  198. package/dist/tool-renderers/read.js +36 -0
  199. package/dist/tool-renderers/repo.d.ts +2 -0
  200. package/dist/tool-renderers/repo.js +13 -0
  201. package/dist/tool-renderers/shell.d.ts +3 -0
  202. package/dist/tool-renderers/shell.js +27 -0
  203. package/dist/tool-renderers/skill.d.ts +2 -0
  204. package/dist/tool-renderers/skill.js +132 -0
  205. package/dist/tool-renderers/subagents.d.ts +2 -0
  206. package/dist/tool-renderers/subagents.js +51 -0
  207. package/dist/tool-renderers/todo.d.ts +2 -0
  208. package/dist/tool-renderers/todo.js +9 -0
  209. package/dist/tool-renderers/types.d.ts +42 -0
  210. package/dist/tool-renderers/types.js +1 -0
  211. package/dist/tool-renderers/utils.d.ts +31 -0
  212. package/dist/tool-renderers/utils.js +230 -0
  213. package/dist/tool-renderers/web.d.ts +3 -0
  214. package/dist/tool-renderers/web.js +9 -0
  215. package/dist/tool-renderers/write.d.ts +2 -0
  216. package/dist/tool-renderers/write.js +42 -0
  217. package/dist/ui.d.ts +56 -0
  218. package/dist/ui.js +94 -0
  219. package/docs/release.md +81 -0
  220. package/extensions/question/contract.ts +100 -0
  221. package/extensions/question/index.ts +34 -0
  222. package/extensions/question/render.ts +28 -0
  223. package/extensions/question/result.ts +86 -0
  224. package/extensions/question/tool-description.ts +11 -0
  225. package/extensions/question/tui.ts +629 -0
  226. package/extensions/question/types.ts +123 -0
  227. package/extensions/session-title/config.ts +169 -0
  228. package/extensions/session-title/index.ts +459 -0
  229. package/extensions/terminal-bell/index.ts +315 -0
  230. package/external/pi-tools-suite/README.md +242 -0
  231. package/external/pi-tools-suite/index.ts +1 -0
  232. package/external/pi-tools-suite/licenses/ollama-pi-web-search.MIT +21 -0
  233. package/external/pi-tools-suite/licenses/opencode-mystatus-MIT.txt +21 -0
  234. package/external/pi-tools-suite/package.json +53 -0
  235. package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +194 -0
  236. package/external/pi-tools-suite/src/antigravity-auth/commands.ts +80 -0
  237. package/external/pi-tools-suite/src/antigravity-auth/constants.ts +26 -0
  238. package/external/pi-tools-suite/src/antigravity-auth/headers.ts +20 -0
  239. package/external/pi-tools-suite/src/antigravity-auth/index.ts +104 -0
  240. package/external/pi-tools-suite/src/antigravity-auth/models.ts +86 -0
  241. package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +305 -0
  242. package/external/pi-tools-suite/src/antigravity-auth/payload.ts +423 -0
  243. package/external/pi-tools-suite/src/antigravity-auth/status.ts +78 -0
  244. package/external/pi-tools-suite/src/antigravity-auth/stream.ts +302 -0
  245. package/external/pi-tools-suite/src/antigravity-auth/types.ts +113 -0
  246. package/external/pi-tools-suite/src/ast-grep/index.ts +6 -0
  247. package/external/pi-tools-suite/src/ast-grep/render.ts +70 -0
  248. package/external/pi-tools-suite/src/ast-grep/schema.ts +109 -0
  249. package/external/pi-tools-suite/src/ast-grep/tool.ts +345 -0
  250. package/external/pi-tools-suite/src/ast-grep/types.ts +55 -0
  251. package/external/pi-tools-suite/src/ast-grep/utils.ts +65 -0
  252. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +222 -0
  253. package/external/pi-tools-suite/src/async-subagents/commands.ts +518 -0
  254. package/external/pi-tools-suite/src/async-subagents/constants.ts +11 -0
  255. package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +74 -0
  256. package/external/pi-tools-suite/src/async-subagents/core/attachment-bridge.ts +133 -0
  257. package/external/pi-tools-suite/src/async-subagents/core/cleanup.ts +66 -0
  258. package/external/pi-tools-suite/src/async-subagents/core/concurrency.ts +90 -0
  259. package/external/pi-tools-suite/src/async-subagents/core/config.ts +819 -0
  260. package/external/pi-tools-suite/src/async-subagents/core/log-limits.ts +166 -0
  261. package/external/pi-tools-suite/src/async-subagents/core/model-fallback.ts +133 -0
  262. package/external/pi-tools-suite/src/async-subagents/core/paths.ts +47 -0
  263. package/external/pi-tools-suite/src/async-subagents/core/pi-invocation.ts +35 -0
  264. package/external/pi-tools-suite/src/async-subagents/core/presets.ts +67 -0
  265. package/external/pi-tools-suite/src/async-subagents/core/process.ts +15 -0
  266. package/external/pi-tools-suite/src/async-subagents/core/prompt.ts +66 -0
  267. package/external/pi-tools-suite/src/async-subagents/core/registry.ts +224 -0
  268. package/external/pi-tools-suite/src/async-subagents/core/retry.ts +191 -0
  269. package/external/pi-tools-suite/src/async-subagents/core/routing.ts +259 -0
  270. package/external/pi-tools-suite/src/async-subagents/core/sessions.ts +138 -0
  271. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +688 -0
  272. package/external/pi-tools-suite/src/async-subagents/core/state.ts +281 -0
  273. package/external/pi-tools-suite/src/async-subagents/core/stop.ts +131 -0
  274. package/external/pi-tools-suite/src/async-subagents/core/structured-result.ts +237 -0
  275. package/external/pi-tools-suite/src/async-subagents/core/tool-guard.ts +34 -0
  276. package/external/pi-tools-suite/src/async-subagents/core/types.ts +150 -0
  277. package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +184 -0
  278. package/external/pi-tools-suite/src/async-subagents/core/utils.ts +11 -0
  279. package/external/pi-tools-suite/src/async-subagents/format.ts +41 -0
  280. package/external/pi-tools-suite/src/async-subagents/index.ts +422 -0
  281. package/external/pi-tools-suite/src/async-subagents/lib.ts +88 -0
  282. package/external/pi-tools-suite/src/async-subagents/live.ts +10 -0
  283. package/external/pi-tools-suite/src/async-subagents/polling.ts +83 -0
  284. package/external/pi-tools-suite/src/async-subagents/render.ts +230 -0
  285. package/external/pi-tools-suite/src/async-subagents/subagent-overlay.ts +77 -0
  286. package/external/pi-tools-suite/src/async-subagents/tasks.ts +120 -0
  287. package/external/pi-tools-suite/src/async-subagents/tools/cleanup.ts +99 -0
  288. package/external/pi-tools-suite/src/async-subagents/tools/result.ts +179 -0
  289. package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +372 -0
  290. package/external/pi-tools-suite/src/async-subagents/tools/status.ts +60 -0
  291. package/external/pi-tools-suite/src/async-subagents/tools/stop.ts +79 -0
  292. package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +152 -0
  293. package/external/pi-tools-suite/src/async-subagents/tools/wait.ts +67 -0
  294. package/external/pi-tools-suite/src/async-subagents/types.ts +45 -0
  295. package/external/pi-tools-suite/src/async-subagents/ui.ts +5 -0
  296. package/external/pi-tools-suite/src/compress/commands.ts +440 -0
  297. package/external/pi-tools-suite/src/compress/compress-tool.ts +368 -0
  298. package/external/pi-tools-suite/src/compress/compression-blocks.ts +524 -0
  299. package/external/pi-tools-suite/src/compress/config.ts +310 -0
  300. package/external/pi-tools-suite/src/compress/dcp-tui-filter.ts +498 -0
  301. package/external/pi-tools-suite/src/compress/index.ts +397 -0
  302. package/external/pi-tools-suite/src/compress/prompts.ts +269 -0
  303. package/external/pi-tools-suite/src/compress/pruner-candidates.ts +176 -0
  304. package/external/pi-tools-suite/src/compress/pruner-compression-blocks.ts +260 -0
  305. package/external/pi-tools-suite/src/compress/pruner-message-ids.ts +147 -0
  306. package/external/pi-tools-suite/src/compress/pruner-metadata.ts +268 -0
  307. package/external/pi-tools-suite/src/compress/pruner-nudge.ts +315 -0
  308. package/external/pi-tools-suite/src/compress/pruner-tools.ts +263 -0
  309. package/external/pi-tools-suite/src/compress/pruner-types.ts +25 -0
  310. package/external/pi-tools-suite/src/compress/pruner.ts +92 -0
  311. package/external/pi-tools-suite/src/compress/state.ts +486 -0
  312. package/external/pi-tools-suite/src/compress/ui.ts +308 -0
  313. package/external/pi-tools-suite/src/config.ts +176 -0
  314. package/external/pi-tools-suite/src/context-usage.ts +31 -0
  315. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +355 -0
  316. package/external/pi-tools-suite/src/index.ts +46 -0
  317. package/external/pi-tools-suite/src/lib/lsp.ts +62 -0
  318. package/external/pi-tools-suite/src/lib/project.ts +42 -0
  319. package/external/pi-tools-suite/src/lib/tool-args.ts +137 -0
  320. package/external/pi-tools-suite/src/lsp/_shared/config.ts +156 -0
  321. package/external/pi-tools-suite/src/lsp/_shared/glob.ts +60 -0
  322. package/external/pi-tools-suite/src/lsp/_shared/output.ts +102 -0
  323. package/external/pi-tools-suite/src/lsp/_shared/paths.ts +138 -0
  324. package/external/pi-tools-suite/src/lsp/_shared/runner.ts +64 -0
  325. package/external/pi-tools-suite/src/lsp/_shared/template.ts +23 -0
  326. package/external/pi-tools-suite/src/lsp/_shared/trust.ts +116 -0
  327. package/external/pi-tools-suite/src/lsp/_shared/types.ts +98 -0
  328. package/external/pi-tools-suite/src/lsp/async.ts +29 -0
  329. package/external/pi-tools-suite/src/lsp/child-process.ts +81 -0
  330. package/external/pi-tools-suite/src/lsp/client.ts +340 -0
  331. package/external/pi-tools-suite/src/lsp/constants.ts +9 -0
  332. package/external/pi-tools-suite/src/lsp/diagnostics-store.ts +64 -0
  333. package/external/pi-tools-suite/src/lsp/documents.ts +24 -0
  334. package/external/pi-tools-suite/src/lsp/index.ts +31 -0
  335. package/external/pi-tools-suite/src/lsp/lsp-utils.ts +37 -0
  336. package/external/pi-tools-suite/src/lsp/manager.ts +190 -0
  337. package/external/pi-tools-suite/src/lsp/mutation-events.ts +78 -0
  338. package/external/pi-tools-suite/src/lsp/renderer.ts +1 -0
  339. package/external/pi-tools-suite/src/lsp/tool-result.ts +6 -0
  340. package/external/pi-tools-suite/src/lsp/tsserver.ts +107 -0
  341. package/external/pi-tools-suite/src/lsp/types.ts +15 -0
  342. package/external/pi-tools-suite/src/model-tools/apply-patch.ts +590 -0
  343. package/external/pi-tools-suite/src/model-tools/index.ts +430 -0
  344. package/external/pi-tools-suite/src/model-tools/path-utils.ts +6 -0
  345. package/external/pi-tools-suite/src/model-tools/tool-args.ts +11 -0
  346. package/external/pi-tools-suite/src/prompt-commands/index.ts +349 -0
  347. package/external/pi-tools-suite/src/repo-discovery/index.ts +384 -0
  348. package/external/pi-tools-suite/src/repo-discovery/project.ts +7 -0
  349. package/external/pi-tools-suite/src/startup-section.ts +13 -0
  350. package/external/pi-tools-suite/src/terminal-bell/index.ts +309 -0
  351. package/external/pi-tools-suite/src/todo/index.ts +201 -0
  352. package/external/pi-tools-suite/src/todo/state/auto-clear.ts +13 -0
  353. package/external/pi-tools-suite/src/todo/state/invariants.ts +21 -0
  354. package/external/pi-tools-suite/src/todo/state/persistence.ts +94 -0
  355. package/external/pi-tools-suite/src/todo/state/replay.ts +38 -0
  356. package/external/pi-tools-suite/src/todo/state/selectors.ts +49 -0
  357. package/external/pi-tools-suite/src/todo/state/state-reducer.ts +362 -0
  358. package/external/pi-tools-suite/src/todo/state/state.ts +18 -0
  359. package/external/pi-tools-suite/src/todo/state/store.ts +52 -0
  360. package/external/pi-tools-suite/src/todo/state/task-graph.ts +57 -0
  361. package/external/pi-tools-suite/src/todo/todo.ts +487 -0
  362. package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +143 -0
  363. package/external/pi-tools-suite/src/todo/tool/types.ts +188 -0
  364. package/external/pi-tools-suite/src/todo/view/format.ts +18 -0
  365. package/external/pi-tools-suite/src/todo/view/labels.ts +13 -0
  366. package/external/pi-tools-suite/src/tool-descriptions.ts +369 -0
  367. package/external/pi-tools-suite/src/usage/index.ts +152 -0
  368. package/external/pi-tools-suite/src/usage/lib/copilot.ts +535 -0
  369. package/external/pi-tools-suite/src/usage/lib/google.ts +478 -0
  370. package/external/pi-tools-suite/src/usage/lib/openai.ts +268 -0
  371. package/external/pi-tools-suite/src/usage/lib/types.ts +114 -0
  372. package/external/pi-tools-suite/src/usage/lib/utils.ts +134 -0
  373. package/external/pi-tools-suite/src/usage/lib/zhipu.ts +228 -0
  374. package/external/pi-tools-suite/src/web-search/index.ts +397 -0
  375. package/package.json +89 -0
  376. package/skills/context7/SKILL.md +69 -0
  377. package/skills/context7/scripts/context7.sh +73 -0
  378. package/skills/handoff/SKILL.md +15 -0
  379. package/skills/pdf/LICENSE.txt +30 -0
  380. package/skills/pdf/SKILL.md +314 -0
  381. package/skills/pdf/forms.md +294 -0
  382. package/skills/pdf/reference.md +612 -0
  383. package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
  384. package/skills/pdf/scripts/check_fillable_fields.py +11 -0
  385. package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
  386. package/skills/pdf/scripts/create_validation_image.py +37 -0
  387. package/skills/pdf/scripts/extract_form_field_info.py +122 -0
  388. package/skills/pdf/scripts/extract_form_structure.py +115 -0
  389. package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
  390. package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  391. package/skills/playwright-cli/SKILL.md +388 -0
  392. package/skills/playwright-cli/references/element-attributes.md +23 -0
  393. package/skills/playwright-cli/references/playwright-tests.md +39 -0
  394. package/skills/playwright-cli/references/request-mocking.md +87 -0
  395. package/skills/playwright-cli/references/running-code.md +241 -0
  396. package/skills/playwright-cli/references/session-management.md +225 -0
  397. package/skills/playwright-cli/references/spec-driven-testing.md +305 -0
  398. package/skills/playwright-cli/references/storage-state.md +275 -0
  399. package/skills/playwright-cli/references/test-generation.md +134 -0
  400. package/skills/playwright-cli/references/tracing.md +139 -0
  401. package/skills/playwright-cli/references/video-recording.md +143 -0
  402. package/skills/simplify/SKILL.md +51 -0
  403. package/skills/skill-creator/LICENSE.txt +202 -0
  404. package/skills/skill-creator/SKILL.md +485 -0
  405. package/skills/skill-creator/agents/analyzer.md +274 -0
  406. package/skills/skill-creator/agents/comparator.md +202 -0
  407. package/skills/skill-creator/agents/grader.md +223 -0
  408. package/skills/skill-creator/assets/eval_review.html +146 -0
  409. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  410. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  411. package/skills/skill-creator/references/schemas.md +430 -0
  412. package/skills/skill-creator/scripts/__init__.py +0 -0
  413. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  414. package/skills/skill-creator/scripts/generate_report.py +326 -0
  415. package/skills/skill-creator/scripts/improve_description.py +247 -0
  416. package/skills/skill-creator/scripts/package_skill.py +136 -0
  417. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  418. package/skills/skill-creator/scripts/run_eval.py +310 -0
  419. package/skills/skill-creator/scripts/run_loop.py +328 -0
  420. package/skills/skill-creator/scripts/utils.py +47 -0
@@ -0,0 +1,156 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
4
+ import { findUp } from "./paths";
5
+ import { askProjectConfigTrust, sha256 } from "./trust";
6
+ import type { ConfigLayer, LoadedConfig, LspConfigFile, LspServerConfig, MatchableConfig } from "./types";
7
+
8
+ function defaultAgentDir(): string {
9
+ return process.env.PI_AGENT_DIR ?? path.join(process.env.HOME ?? "", ".pi", "agent");
10
+ }
11
+
12
+ function asObject(value: unknown): Record<string, unknown> | undefined {
13
+ return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
14
+ }
15
+
16
+ function cleanStringArray(value: unknown): string[] | undefined {
17
+ if (!Array.isArray(value)) return undefined;
18
+ return value.filter((item): item is string => typeof item === "string");
19
+ }
20
+
21
+ function cleanStringRecord(value: unknown): Record<string, string> | undefined {
22
+ const object = asObject(value);
23
+ if (!object) return undefined;
24
+ const out: Record<string, string> = {};
25
+ for (const [key, recordValue] of Object.entries(object)) {
26
+ if (typeof recordValue === "string") out[key] = recordValue;
27
+ }
28
+ return out;
29
+ }
30
+
31
+ function cleanNumber(value: unknown): number | undefined {
32
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
33
+ }
34
+
35
+ function cleanBoolean(value: unknown): boolean | undefined {
36
+ return typeof value === "boolean" ? value : undefined;
37
+ }
38
+
39
+ function cleanMatchable<T extends MatchableConfig>(object: Record<string, unknown>, extra: Omit<T, keyof MatchableConfig>): T | undefined {
40
+ if (typeof object.id !== "string" || object.id.trim() === "") return undefined;
41
+ return {
42
+ id: object.id,
43
+ enabled: cleanBoolean(object.enabled),
44
+ include: cleanStringArray(object.include),
45
+ exclude: cleanStringArray(object.exclude),
46
+ rootMarkers: cleanStringArray(object.rootMarkers),
47
+ maxFileSizeBytes: cleanNumber(object.maxFileSizeBytes),
48
+ ...extra,
49
+ } as T;
50
+ }
51
+
52
+ function parseLspItems(parsed: unknown): LspServerConfig[] {
53
+ const root = asObject(parsed) as LspConfigFile | undefined;
54
+ const servers = Array.isArray(root?.servers) ? root.servers : [];
55
+ const out: LspServerConfig[] = [];
56
+
57
+ for (const item of servers) {
58
+ const object = asObject(item);
59
+ if (!object) continue;
60
+ const bin = typeof object.bin === "string" ? object.bin : "";
61
+ if (object.enabled !== false && bin.trim() === "") continue;
62
+
63
+ const cleaned = cleanMatchable<LspServerConfig>(object, {
64
+ bin,
65
+ args: cleanStringArray(object.args),
66
+ cwd: typeof object.cwd === "string" ? object.cwd : undefined,
67
+ env: cleanStringRecord(object.env),
68
+ config: typeof object.config === "string" ? object.config : undefined,
69
+ languageIdByExtension: cleanStringRecord(object.languageIdByExtension),
70
+ startupTimeoutMs: cleanNumber(object.startupTimeoutMs),
71
+ diagnosticsWaitMs: cleanNumber(object.diagnosticsWaitMs),
72
+ initializationOptions: object.initializationOptions,
73
+ settings: object.settings,
74
+ });
75
+ if (cleaned) out.push(cleaned);
76
+ }
77
+
78
+ return out;
79
+ }
80
+
81
+ async function readJsonLayer<TItem extends MatchableConfig>(options: {
82
+ scope: "global" | "project";
83
+ filePath: string;
84
+ parseItems: (parsed: unknown) => TItem[];
85
+ }): Promise<ConfigLayer<TItem> | undefined> {
86
+ let raw: string;
87
+ try {
88
+ raw = await fs.readFile(options.filePath, "utf8");
89
+ } catch (error) {
90
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
91
+ throw error;
92
+ }
93
+
94
+ const parsed = JSON.parse(raw) as unknown;
95
+ return {
96
+ scope: options.scope,
97
+ path: options.filePath,
98
+ dir: path.dirname(options.filePath),
99
+ raw,
100
+ hash: sha256(raw),
101
+ items: options.parseItems(parsed),
102
+ };
103
+ }
104
+
105
+ function mergeLayers<TItem extends MatchableConfig>(layers: ConfigLayer<TItem>[]): TItem[] {
106
+ const byId = new Map<string, TItem>();
107
+ for (const layer of layers) {
108
+ for (const item of layer.items) {
109
+ if (item.enabled === false) {
110
+ byId.delete(item.id);
111
+ continue;
112
+ }
113
+ byId.set(item.id, item);
114
+ }
115
+ }
116
+ return [...byId.values()];
117
+ }
118
+
119
+ function binariesForLsp(items: LspServerConfig[]): string[] {
120
+ return items.map((item) => item.bin).filter(Boolean);
121
+ }
122
+
123
+ export async function loadLspConfig(ctx: ExtensionContext): Promise<LoadedConfig<LspServerConfig>> {
124
+ const warnings: string[] = [];
125
+ const layers: ConfigLayer<LspServerConfig>[] = [];
126
+ const globalPath = path.join(defaultAgentDir(), "lsp.json");
127
+
128
+ try {
129
+ const globalLayer = await readJsonLayer({ scope: "global", filePath: globalPath, parseItems: parseLspItems });
130
+ if (globalLayer) layers.push(globalLayer);
131
+ } catch (error) {
132
+ warnings.push(`Failed to load global lsp config: ${(error as Error).message}`);
133
+ }
134
+
135
+ const projectPath = findUp(ctx.cwd, path.join(".pi", "lsp.json"));
136
+ if (projectPath) {
137
+ try {
138
+ const projectLayer = await readJsonLayer({ scope: "project", filePath: projectPath, parseItems: parseLspItems });
139
+ if (projectLayer) {
140
+ const decision = await askProjectConfigTrust({
141
+ ctx,
142
+ kind: "lsp",
143
+ configPath: projectLayer.path,
144
+ hash: projectLayer.hash,
145
+ binaries: binariesForLsp(projectLayer.items),
146
+ });
147
+ if (decision.trusted) layers.push(projectLayer);
148
+ else warnings.push(`${projectLayer.path}: ${decision.reason ?? "project-local config rejected"}`);
149
+ }
150
+ } catch (error) {
151
+ warnings.push(`Failed to load project lsp config: ${(error as Error).message}`);
152
+ }
153
+ }
154
+
155
+ return { items: mergeLayers(layers), layers, warnings };
156
+ }
@@ -0,0 +1,60 @@
1
+ function normalizePathForGlob(input: string): string {
2
+ return input.replace(/\\/g, "/").replace(/^\.\//, "");
3
+ }
4
+
5
+ function escapeRegExpChar(char: string): string {
6
+ return /[|\\{}()[\]^$+?.]/.test(char) ? `\\${char}` : char;
7
+ }
8
+
9
+ export function globToRegExp(pattern: string): RegExp {
10
+ const normalized = normalizePathForGlob(pattern);
11
+ let source = "^";
12
+
13
+ for (let i = 0; i < normalized.length; i += 1) {
14
+ const char = normalized[i];
15
+ const next = normalized[i + 1];
16
+
17
+ if (char === "*") {
18
+ if (next === "*") {
19
+ const after = normalized[i + 2];
20
+ if (after === "/") {
21
+ source += "(?:.*\/)?";
22
+ i += 2;
23
+ } else {
24
+ source += ".*";
25
+ i += 1;
26
+ }
27
+ } else {
28
+ source += "[^/]*";
29
+ }
30
+ continue;
31
+ }
32
+
33
+ if (char === "?") {
34
+ source += "[^/]";
35
+ continue;
36
+ }
37
+
38
+ source += escapeRegExpChar(char);
39
+ }
40
+
41
+ source += "$";
42
+ return new RegExp(source);
43
+ }
44
+
45
+ export function matchesGlob(pattern: string, relativePath: string): boolean {
46
+ const normalizedPath = normalizePathForGlob(relativePath);
47
+ return globToRegExp(pattern).test(normalizedPath);
48
+ }
49
+
50
+ export function matchesAnyGlob(patterns: string[] | undefined, relativePath: string): boolean {
51
+ if (!patterns || patterns.length === 0) return false;
52
+ return patterns.some((pattern) => matchesGlob(pattern, relativePath));
53
+ }
54
+
55
+ export function isPathIncluded(relativePath: string, include?: string[], exclude?: string[]): boolean {
56
+ const normalizedPath = normalizePathForGlob(relativePath);
57
+ const included = !include || include.length === 0 || matchesAnyGlob(include, normalizedPath);
58
+ if (!included) return false;
59
+ return !matchesAnyGlob(exclude, normalizedPath);
60
+ }
@@ -0,0 +1,102 @@
1
+ import path from "node:path";
2
+ import type { Diagnostic, DiagnosticRelatedInformation } from "vscode-languageserver-protocol";
3
+ import type { CommandRunResult } from "./types";
4
+ import { normalizeRelativePath, uriToFilePath } from "./paths";
5
+
6
+ const DEFAULT_OUTPUT_LIMIT = 4000;
7
+
8
+ export function textFromContent(content: Array<{ type: string; text?: string }>): string {
9
+ return content
10
+ .filter((item): item is { type: "text"; text: string } => item.type === "text" && typeof item.text === "string")
11
+ .map((item) => item.text)
12
+ .join("\n");
13
+ }
14
+
15
+ export function appendTextContent<T extends Array<{ type: string; text?: string }>>(content: T, text: string): T {
16
+ if (!text.trim()) return content;
17
+ return [...content, { type: "text", text }] as T;
18
+ }
19
+
20
+ export function truncateOutput(output: string, limit = DEFAULT_OUTPUT_LIMIT): string {
21
+ const normalized = output.trim();
22
+ if (normalized.length <= limit) return normalized;
23
+ return `${normalized.slice(0, limit)}\n… output truncated (${normalized.length - limit} more characters)`;
24
+ }
25
+
26
+ export function commandOutput(result: Pick<CommandRunResult, "stdout" | "stderr">): string {
27
+ return truncateOutput([result.stdout, result.stderr].filter((part) => part.trim().length > 0).join("\n"));
28
+ }
29
+
30
+ export function formatWarnings(title: string, warnings: string[]): string {
31
+ if (warnings.length === 0) return "";
32
+ return `${title}:\n\n${warnings.map((warning) => `⚠️ ${warning}`).join("\n")}`;
33
+ }
34
+
35
+ export function formatCommandIssue(toolId: string, action: string, result: CommandRunResult): string {
36
+ const output = commandOutput(result);
37
+ const suffix = result.killed ? " (killed/timeout)" : "";
38
+ if (!output) return `⚠️ ${toolId} ${action} failed with exit code ${result.code}${suffix}`;
39
+ return `⚠️ ${toolId} ${action} failed with exit code ${result.code}${suffix}:\n${output}`;
40
+ }
41
+
42
+ export function formatDiagnosticOutput(toolId: string, output: string): string {
43
+ const compact = truncateOutput(output);
44
+ if (!compact) return `⚠️ ${toolId} found issues`;
45
+ return `⚠️ ${toolId} found issues:\n${compact}`;
46
+ }
47
+
48
+ function severityLabel(severity: number | undefined): string {
49
+ switch (severity) {
50
+ case 1:
51
+ return "error";
52
+ case 2:
53
+ return "warning";
54
+ case 3:
55
+ return "info";
56
+ case 4:
57
+ return "hint";
58
+ default:
59
+ return "diagnostic";
60
+ }
61
+ }
62
+
63
+ function relatedInformationSuffix(relatedInformation: DiagnosticRelatedInformation[] | undefined): string[] {
64
+ if (!relatedInformation || relatedInformation.length === 0) return [];
65
+ return relatedInformation.slice(0, 3).map((item) => {
66
+ let file = item.location.uri;
67
+ try {
68
+ file = uriToFilePath(item.location.uri);
69
+ } catch {
70
+ // keep URI as-is
71
+ }
72
+ return ` related: ${file}:${item.location.range.start.line + 1}:${item.location.range.start.character + 1} ${item.message}`;
73
+ });
74
+ }
75
+
76
+ export function formatDiagnostic(file: string, diagnostic: Diagnostic, root?: string): string {
77
+ const displayPath = root ? normalizeRelativePath(path.relative(root, file) || path.basename(file)) : file;
78
+ const line = diagnostic.range.start.line + 1;
79
+ const character = diagnostic.range.start.character + 1;
80
+ const source = diagnostic.source ? `${diagnostic.source}: ` : "";
81
+ const code = diagnostic.code === undefined ? "" : ` [${String(diagnostic.code)}]`;
82
+ const firstLine = `${displayPath}:${line}:${character} - ${severityLabel(diagnostic.severity)}: ${source}${diagnostic.message}${code}`;
83
+ const related = relatedInformationSuffix(diagnostic.relatedInformation);
84
+ return [firstLine, ...related].join("\n");
85
+ }
86
+
87
+ export function formatLspDiagnostics(serverId: string, file: string, diagnostics: Diagnostic[], root?: string): string {
88
+ if (diagnostics.length === 0) return "";
89
+ const rendered = diagnostics.slice(0, 20).map((diagnostic) => formatDiagnostic(file, diagnostic, root));
90
+ if (diagnostics.length > 20) rendered.push(`… ${diagnostics.length - 20} more diagnostics`);
91
+ return `⚠️ ${serverId}:\n${rendered.join("\n")}`;
92
+ }
93
+
94
+ export function hasIssueOutput(output: string): boolean {
95
+ return output.includes("⚠️");
96
+ }
97
+
98
+ export function joinSections(title: string, lines: string[]): string {
99
+ const nonEmpty = lines.filter((line) => line.trim().length > 0);
100
+ if (nonEmpty.length === 0) return "";
101
+ return `${title}:\n\n${nonEmpty.join("\n")}`;
102
+ }
@@ -0,0 +1,138 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import type { CommandConfig, PathPlaceholders, ResolvedCommand } from "./types";
5
+ import { applyTemplate, applyTemplateArray, applyTemplateRecord } from "./template";
6
+
7
+ export function expandHome(input: string): string {
8
+ if (input === "~") return process.env.HOME ?? input;
9
+ if (input.startsWith("~/")) return path.join(process.env.HOME ?? "~", input.slice(2));
10
+ return input;
11
+ }
12
+
13
+ export function toAbsolutePath(inputPath: string, cwd: string): string {
14
+ const expanded = expandHome(inputPath);
15
+ return path.isAbsolute(expanded) ? path.normalize(expanded) : path.resolve(cwd, expanded);
16
+ }
17
+
18
+ export function normalizeRelativePath(inputPath: string): string {
19
+ const normalized = inputPath.split(path.sep).join("/");
20
+ return normalized === "" ? "." : normalized;
21
+ }
22
+
23
+ export function isSubPathOrSame(parent: string, child: string): boolean {
24
+ const relative = path.relative(parent, child);
25
+ return relative === "" || (!!relative && !relative.startsWith("..") && !path.isAbsolute(relative));
26
+ }
27
+
28
+ export function markerExists(candidateDir: string, marker: string): boolean {
29
+ return fs.existsSync(path.resolve(candidateDir, marker));
30
+ }
31
+
32
+ export function findProjectRoot(filePath: string, rootMarkers: string[] | undefined, fallbackRoot: string): string | undefined {
33
+ const absoluteFile = toAbsolutePath(filePath, fallbackRoot);
34
+ let dir = fs.existsSync(absoluteFile) && fs.statSync(absoluteFile).isDirectory() ? absoluteFile : path.dirname(absoluteFile);
35
+ const markers = rootMarkers?.filter(Boolean) ?? [];
36
+
37
+ if (markers.length === 0) {
38
+ return toAbsolutePath(fallbackRoot, process.cwd());
39
+ }
40
+
41
+ while (true) {
42
+ if (markers.some((marker) => markerExists(dir, marker))) {
43
+ return dir;
44
+ }
45
+
46
+ const parent = path.dirname(dir);
47
+ if (parent === dir) return undefined;
48
+ dir = parent;
49
+ }
50
+ }
51
+
52
+ export function findUp(startDir: string, relativeFile: string): string | undefined {
53
+ let dir = toAbsolutePath(startDir, process.cwd());
54
+ while (true) {
55
+ const candidate = path.join(dir, relativeFile);
56
+ if (fs.existsSync(candidate)) return candidate;
57
+ const parent = path.dirname(dir);
58
+ if (parent === dir) return undefined;
59
+ dir = parent;
60
+ }
61
+ }
62
+
63
+ function resolveConfigPath(config: string | undefined, root: string, baseValues: PathPlaceholders): string | undefined {
64
+ if (!config) return undefined;
65
+ const templated = applyTemplate(config, baseValues);
66
+ const expanded = expandHome(templated);
67
+ return path.isAbsolute(expanded) ? path.normalize(expanded) : path.resolve(root, expanded);
68
+ }
69
+
70
+ export function createPathPlaceholders(options: {
71
+ workspace: string;
72
+ root: string;
73
+ file: string;
74
+ config?: string;
75
+ }): PathPlaceholders {
76
+ const workspace = toAbsolutePath(options.workspace, process.cwd());
77
+ const root = toAbsolutePath(options.root, workspace);
78
+ const file = toAbsolutePath(options.file, root);
79
+ const dir = path.dirname(file);
80
+ const relFile = normalizeRelativePath(path.relative(root, file));
81
+ const relDir = normalizeRelativePath(path.relative(root, dir));
82
+ const config = options.config ? toAbsolutePath(options.config, root) : "";
83
+ const configDir = config ? path.dirname(config) : "";
84
+
85
+ return { workspace, root, file, relFile, dir, relDir, config, configDir };
86
+ }
87
+
88
+ export function resolveExecutable(bin: string, root: string): string {
89
+ const expanded = expandHome(bin);
90
+ if (path.isAbsolute(expanded)) return path.normalize(expanded);
91
+ if (expanded.includes("/") || expanded.includes("\\")) return path.resolve(root, expanded);
92
+ return expanded;
93
+ }
94
+
95
+ export function resolveWorkingDirectory(cwd: string | undefined, root: string, values: PathPlaceholders): string {
96
+ if (!cwd) return root;
97
+ const templated = applyTemplate(cwd, values);
98
+ const expanded = expandHome(templated);
99
+ return path.isAbsolute(expanded) ? path.normalize(expanded) : path.resolve(root, expanded);
100
+ }
101
+
102
+ export function resolveCommand(id: string, command: CommandConfig, options: {
103
+ workspace: string;
104
+ root: string;
105
+ file: string;
106
+ }): ResolvedCommand {
107
+ const baseValues = createPathPlaceholders({
108
+ workspace: options.workspace,
109
+ root: options.root,
110
+ file: options.file,
111
+ });
112
+ const configPath = resolveConfigPath(command.config, options.root, baseValues);
113
+ const values = createPathPlaceholders({
114
+ workspace: options.workspace,
115
+ root: options.root,
116
+ file: options.file,
117
+ config: configPath,
118
+ });
119
+
120
+ return {
121
+ id,
122
+ bin: resolveExecutable(applyTemplate(command.bin, values), options.root),
123
+ args: applyTemplateArray(command.args, values),
124
+ cwd: resolveWorkingDirectory(command.cwd, options.root, values),
125
+ env: applyTemplateRecord(command.env, values),
126
+ timeoutMs: command.timeoutMs,
127
+ configPath,
128
+ placeholders: values,
129
+ };
130
+ }
131
+
132
+ export function filePathToUri(filePath: string): string {
133
+ return pathToFileURL(path.resolve(filePath)).toString();
134
+ }
135
+
136
+ export function uriToFilePath(uri: string): string {
137
+ return fileURLToPath(uri);
138
+ }
@@ -0,0 +1,64 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
+ import type { CommandRunResult, ResolvedCommand } from "./types";
5
+
6
+ function isExecutable(filePath: string): boolean {
7
+ try {
8
+ fs.accessSync(filePath, fs.constants.X_OK);
9
+ return true;
10
+ } catch {
11
+ return false;
12
+ }
13
+ }
14
+
15
+ function pathCandidates(bin: string): string[] {
16
+ const pathEntries = (process.env.PATH ?? "").split(path.delimiter).filter(Boolean);
17
+ const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";") : [""];
18
+ const candidates: string[] = [];
19
+ for (const entry of pathEntries) {
20
+ for (const extension of extensions) {
21
+ candidates.push(path.join(entry, `${bin}${extension}`));
22
+ }
23
+ }
24
+ return candidates;
25
+ }
26
+
27
+ export function findExecutable(bin: string): string | undefined {
28
+ if (path.isAbsolute(bin) || bin.includes("/") || bin.includes("\\")) {
29
+ return isExecutable(bin) ? bin : undefined;
30
+ }
31
+ return pathCandidates(bin).find(isExecutable);
32
+ }
33
+
34
+ export function isExecutableAvailable(bin: string): boolean {
35
+ return !!findExecutable(bin);
36
+ }
37
+
38
+ export async function runCommand(
39
+ pi: ExtensionAPI,
40
+ command: ResolvedCommand,
41
+ signal?: AbortSignal,
42
+ ): Promise<CommandRunResult> {
43
+ const startedAt = Date.now();
44
+ const execOptions = {
45
+ cwd: command.cwd,
46
+ timeout: command.timeoutMs,
47
+ signal,
48
+ env: command.env ? { ...process.env, ...command.env } : undefined,
49
+ } as Parameters<ExtensionAPI["exec"]>[2] & { env?: NodeJS.ProcessEnv };
50
+
51
+ const result = await pi.exec(command.bin, command.args, execOptions);
52
+
53
+ return {
54
+ id: command.id,
55
+ bin: command.bin,
56
+ args: command.args,
57
+ cwd: command.cwd,
58
+ stdout: result.stdout,
59
+ stderr: result.stderr,
60
+ code: result.code,
61
+ killed: result.killed,
62
+ durationMs: Date.now() - startedAt,
63
+ };
64
+ }
@@ -0,0 +1,23 @@
1
+ import type { PathPlaceholders } from "./types";
2
+
3
+ const PLACEHOLDER_PATTERN = /\{(workspace|root|file|relFile|dir|relDir|config|configDir)\}/g;
4
+
5
+ export function applyTemplate(input: string, values: PathPlaceholders): string {
6
+ return input.replace(PLACEHOLDER_PATTERN, (_match, key: keyof PathPlaceholders) => values[key] ?? "");
7
+ }
8
+
9
+ export function applyTemplateArray(inputs: string[] | undefined, values: PathPlaceholders): string[] {
10
+ return (inputs ?? []).map((input) => applyTemplate(input, values));
11
+ }
12
+
13
+ export function applyTemplateRecord(
14
+ inputs: Record<string, string> | undefined,
15
+ values: PathPlaceholders,
16
+ ): Record<string, string> | undefined {
17
+ if (!inputs) return undefined;
18
+ const out: Record<string, string> = {};
19
+ for (const [key, value] of Object.entries(inputs)) {
20
+ out[key] = applyTemplate(value, values);
21
+ }
22
+ return out;
23
+ }
@@ -0,0 +1,116 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
5
+ import type { ConfigKind } from "./types";
6
+
7
+ export interface TrustStore {
8
+ version: 1;
9
+ trustedHashes: string[];
10
+ }
11
+
12
+ export interface TrustDecision {
13
+ trusted: boolean;
14
+ persist: boolean;
15
+ reason?: string;
16
+ }
17
+
18
+ const sessionTrustedHashes = new Set<string>();
19
+
20
+ function sessionTrustKey(kind: ConfigKind, hash: string): string {
21
+ return `${kind}:${hash}`;
22
+ }
23
+
24
+ export function sha256(content: string): string {
25
+ return crypto.createHash("sha256").update(content).digest("hex");
26
+ }
27
+
28
+ export function getTrustStorePath(kind: ConfigKind, agentDir = process.env.PI_AGENT_DIR ?? path.join(process.env.HOME ?? "", ".pi", "agent")): string {
29
+ return path.join(agentDir, "trust", `${kind}.json`);
30
+ }
31
+
32
+ async function readTrustStore(storePath: string): Promise<TrustStore> {
33
+ try {
34
+ const raw = await fs.readFile(storePath, "utf8");
35
+ const parsed = JSON.parse(raw) as Partial<TrustStore>;
36
+ return {
37
+ version: 1,
38
+ trustedHashes: Array.isArray(parsed.trustedHashes) ? parsed.trustedHashes.filter((item): item is string => typeof item === "string") : [],
39
+ };
40
+ } catch (error) {
41
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
42
+ return { version: 1, trustedHashes: [] };
43
+ }
44
+ throw error;
45
+ }
46
+ }
47
+
48
+ async function writeTrustStore(storePath: string, store: TrustStore): Promise<void> {
49
+ await fs.mkdir(path.dirname(storePath), { recursive: true });
50
+ await fs.writeFile(storePath, `${JSON.stringify(store, null, 2)}\n`, "utf8");
51
+ }
52
+
53
+ export async function isHashTrusted(kind: ConfigKind, hash: string): Promise<boolean> {
54
+ const store = await readTrustStore(getTrustStorePath(kind));
55
+ return store.trustedHashes.includes(hash);
56
+ }
57
+
58
+ export async function rememberTrustedHash(kind: ConfigKind, hash: string): Promise<void> {
59
+ const storePath = getTrustStorePath(kind);
60
+ const store = await readTrustStore(storePath);
61
+ if (!store.trustedHashes.includes(hash)) {
62
+ store.trustedHashes.push(hash);
63
+ await writeTrustStore(storePath, store);
64
+ }
65
+ }
66
+
67
+ function formatBinaryList(binaries: string[]): string {
68
+ const unique = [...new Set(binaries.filter(Boolean))];
69
+ if (unique.length === 0) return " (no binaries declared)";
70
+ return unique.map((bin) => ` - ${bin}`).join("\n");
71
+ }
72
+
73
+ export async function askProjectConfigTrust(options: {
74
+ ctx: ExtensionContext;
75
+ kind: ConfigKind;
76
+ configPath: string;
77
+ hash: string;
78
+ binaries: string[];
79
+ }): Promise<TrustDecision> {
80
+ if (sessionTrustedHashes.has(sessionTrustKey(options.kind, options.hash))) {
81
+ return { trusted: true, persist: false };
82
+ }
83
+
84
+ if (await isHashTrusted(options.kind, options.hash)) {
85
+ return { trusted: true, persist: true };
86
+ }
87
+
88
+ if (!options.ctx.hasUI) {
89
+ return { trusted: false, persist: false, reason: "project-local config rejected in non-interactive mode" };
90
+ }
91
+
92
+ const title = [
93
+ `Project-local ${options.kind} config wants to auto-run binaries.`,
94
+ "",
95
+ `Config: ${options.configPath}`,
96
+ `Hash: ${options.hash}`,
97
+ "",
98
+ "Binaries:",
99
+ formatBinaryList(options.binaries),
100
+ "",
101
+ "Trust this config?",
102
+ ].join("\n");
103
+
104
+ const choice = await options.ctx.ui.select(title, ["Trust once", "Trust always", "Reject"]);
105
+
106
+ if (choice === "Trust once") {
107
+ sessionTrustedHashes.add(sessionTrustKey(options.kind, options.hash));
108
+ return { trusted: true, persist: false };
109
+ }
110
+ if (choice === "Trust always") {
111
+ await rememberTrustedHash(options.kind, options.hash);
112
+ return { trusted: true, persist: true };
113
+ }
114
+
115
+ return { trusted: false, persist: false, reason: "project-local config rejected by user" };
116
+ }