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,835 @@
1
+ /**
2
+ * InputEditor — multiline text input with cursor, selection, word ops, and paste.
3
+ *
4
+ * Supports attachments: images, pasted text blocks, and file references are
5
+ * tracked as virtual tags in the text (e.g. `[Image 1]`, `[Pasted 42 lines]`,
6
+ * `[File: foo.ts]`). The actual data is stored separately and collected on submit.
7
+ */
8
+ export { imageMimeTypeForPath, isImagePath, looksLikeFilePath, quoteFilePathForInput, readClipboardImage } from "./input-editor-files.js";
9
+ // ── Constants ───────────────────────────────────────────────────────
10
+ const PASTE_SUMMARY_MIN_LINES = 2;
11
+ const INPUT_UNDO_MAX_STATES = 64;
12
+ const INPUT_UNDO_MAX_TEXT_CHARS = 128 * 1024;
13
+ const INPUT_UNDO_MAX_ATTACHMENT_REFS = 256;
14
+ const INPUT_UNDO_MAX_SNAPSHOT_TEXT_CHARS = 32 * 1024;
15
+ const INPUT_UNDO_MAX_SNAPSHOT_ATTACHMENT_REFS = 64;
16
+ // ── InputEditor ─────────────────────────────────────────────────────
17
+ export class InputEditor {
18
+ _text = "";
19
+ _cursor = 0;
20
+ _selection;
21
+ _bracketedPasteDepth = 0;
22
+ _scrollOffset;
23
+ _attachments = [];
24
+ _imageCounter = 0;
25
+ _pasteCounter = 0;
26
+ _undoStack = [];
27
+ _redoStack = [];
28
+ _historyMutationDepth = 0;
29
+ _restoringHistory = false;
30
+ // ── public getters ──────────────────────────────────────────────
31
+ get text() { return this._text; }
32
+ get cursor() { return this._cursor; }
33
+ get selection() { return this._selection; }
34
+ get hasSelection() {
35
+ return !!this._selection && this._selection.anchor !== this._selection.active;
36
+ }
37
+ get attachments() { return this._attachments; }
38
+ get hasAttachments() { return this._attachments.length > 0; }
39
+ get canUndo() { return this._undoStack.length > 0; }
40
+ get canRedo() { return this._redoStack.length > 0; }
41
+ /** Get only image attachments. */
42
+ get images() {
43
+ const images = [];
44
+ for (const a of this._attachments) {
45
+ if (a.kind === "image")
46
+ images.push(a.image);
47
+ else if (a.kind === "file" && a.image)
48
+ images.push(a.image);
49
+ }
50
+ return images;
51
+ }
52
+ /** Get the text to submit — virtual attachment tags are expanded or removed. */
53
+ get expandedText() {
54
+ let result = this._text;
55
+ for (const att of this._attachments) {
56
+ if (att.kind === "pasted-text") {
57
+ result = result.replace(att.tag, att.text);
58
+ }
59
+ else if (att.kind === "file" && att.content !== undefined) {
60
+ result = result.replace(att.tag, att.content);
61
+ }
62
+ else {
63
+ result = removeVirtualAttachmentTag(result, att.tag);
64
+ }
65
+ }
66
+ return result;
67
+ }
68
+ /**
69
+ * Get the text to send to the SDK.
70
+ *
71
+ * Like opencode, image attachments keep their virtual marker in the text
72
+ * while the actual image is sent separately. That keeps image-only prompts
73
+ * non-empty without showing the marker in our rendered user message.
74
+ */
75
+ get promptText() {
76
+ let result = this._text;
77
+ for (const att of this._attachments) {
78
+ if (att.kind === "pasted-text") {
79
+ result = result.replace(att.tag, att.text);
80
+ }
81
+ else if (att.kind === "file" && att.content !== undefined) {
82
+ result = result.replace(att.tag, att.content);
83
+ }
84
+ }
85
+ return result;
86
+ }
87
+ // ── mutations ───────────────────────────────────────────────────
88
+ setText(text, cursor) {
89
+ this.recordEdit(() => {
90
+ this._text = text;
91
+ this._cursor = cursor ?? text.length;
92
+ this.clampCursor();
93
+ this.clearSelection();
94
+ });
95
+ }
96
+ clear() {
97
+ this._text = "";
98
+ this._cursor = 0;
99
+ this.clearSelection();
100
+ this.clearScrollOffset();
101
+ this._attachments.length = 0;
102
+ this._imageCounter = 0;
103
+ this._pasteCounter = 0;
104
+ this.clearHistory();
105
+ }
106
+ undo() {
107
+ const previous = this._undoStack.pop();
108
+ if (!previous)
109
+ return false;
110
+ const current = this.captureHistorySnapshot();
111
+ if (this.isRecordableHistorySnapshot(current)) {
112
+ this._redoStack.push(current);
113
+ }
114
+ else {
115
+ this._redoStack.length = 0;
116
+ }
117
+ this.restoreHistorySnapshot(previous);
118
+ this.trimHistory();
119
+ return true;
120
+ }
121
+ redo() {
122
+ const next = this._redoStack.pop();
123
+ if (!next)
124
+ return false;
125
+ const current = this.captureHistorySnapshot();
126
+ if (this.isRecordableHistorySnapshot(current)) {
127
+ this._undoStack.push(current);
128
+ }
129
+ else {
130
+ this._undoStack.length = 0;
131
+ }
132
+ this.restoreHistorySnapshot(next);
133
+ this.trimHistory();
134
+ return true;
135
+ }
136
+ /** Insert text at the cursor, replacing any active selection. */
137
+ insert(text) {
138
+ if (!text && !this.hasSelection)
139
+ return;
140
+ this.recordEdit(() => {
141
+ this.deleteSelection();
142
+ const before = this._text.slice(0, this._cursor);
143
+ const after = this._text.slice(this._cursor);
144
+ this._text = before + text + after;
145
+ this._cursor += text.length;
146
+ });
147
+ }
148
+ deleteBackward() {
149
+ this.recordEdit(() => {
150
+ if (this.hasSelection) {
151
+ this.deleteSelection();
152
+ return;
153
+ }
154
+ if (this._cursor <= 0)
155
+ return;
156
+ if (this.removeAttachmentForBackwardDelete())
157
+ return;
158
+ this._text = this._text.slice(0, this._cursor - 1) + this._text.slice(this._cursor);
159
+ this._cursor -= 1;
160
+ });
161
+ }
162
+ deleteForward() {
163
+ this.recordEdit(() => {
164
+ if (this.hasSelection) {
165
+ this.deleteSelection();
166
+ return;
167
+ }
168
+ if (this._cursor >= this._text.length)
169
+ return;
170
+ if (this.removeAttachmentForForwardDelete())
171
+ return;
172
+ this._text = this._text.slice(0, this._cursor) + this._text.slice(this._cursor + 1);
173
+ });
174
+ }
175
+ deleteToLineStart() {
176
+ this.recordEdit(() => {
177
+ if (this.hasSelection) {
178
+ this.deleteSelection();
179
+ return;
180
+ }
181
+ const lineStart = this.findLineStart(this._cursor);
182
+ if (lineStart === this._cursor)
183
+ return;
184
+ this._text = this._text.slice(0, lineStart) + this._text.slice(this._cursor);
185
+ this._cursor = lineStart;
186
+ });
187
+ }
188
+ deleteToLineStartOrPreviousLineEnd() {
189
+ this.recordEdit(() => {
190
+ if (this.hasSelection) {
191
+ this.deleteSelection();
192
+ return;
193
+ }
194
+ const lineStart = this.findLineStart(this._cursor);
195
+ if (lineStart < this._cursor) {
196
+ const deleteStart = lineStart > 0 ? lineStart - 1 : lineStart;
197
+ this._text = this._text.slice(0, deleteStart) + this._text.slice(this._cursor);
198
+ this._cursor = deleteStart;
199
+ return;
200
+ }
201
+ if (this._cursor <= 0)
202
+ return;
203
+ const deleteStart = this._cursor - 1;
204
+ this._text = this._text.slice(0, deleteStart) + this._text.slice(this._cursor);
205
+ this._cursor = deleteStart;
206
+ });
207
+ }
208
+ deleteWordBackward() {
209
+ this.recordEdit(() => {
210
+ if (this.hasSelection) {
211
+ this.deleteSelection();
212
+ return;
213
+ }
214
+ if (this._cursor <= 0)
215
+ return;
216
+ const wordStart = this.findWordStart(this._cursor);
217
+ this._text = this._text.slice(0, wordStart) + this._text.slice(this._cursor);
218
+ this._cursor = wordStart;
219
+ });
220
+ }
221
+ deleteWordForward() {
222
+ this.recordEdit(() => {
223
+ if (this.hasSelection) {
224
+ this.deleteSelection();
225
+ return;
226
+ }
227
+ if (this._cursor >= this._text.length)
228
+ return;
229
+ const wordEnd = this.findWordEnd(this._cursor);
230
+ this._text = this._text.slice(0, this._cursor) + this._text.slice(wordEnd);
231
+ });
232
+ }
233
+ // ── attachment operations ───────────────────────────────────────
234
+ /**
235
+ * Attach an image. Inserts a virtual tag `[Image N]` at the cursor.
236
+ */
237
+ attachImage(data, mimeType) {
238
+ this.recordEdit(() => {
239
+ this._imageCounter += 1;
240
+ const tag = `[Image ${this._imageCounter}]`;
241
+ this._attachments.push({ kind: "image", tag, image: { type: "image", data, mimeType } });
242
+ this.insert(`${tag} `);
243
+ });
244
+ }
245
+ /**
246
+ * Attach pasted multi-line text. Inserts a virtual tag `[Pasted ~N lines]`
247
+ * at the cursor if the text exceeds the threshold; otherwise inserts inline.
248
+ */
249
+ attachPastedText(text) {
250
+ this.recordEdit(() => {
251
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
252
+ const lineCount = (normalized.match(/\n/g)?.length ?? 0) + 1;
253
+ if (lineCount >= PASTE_SUMMARY_MIN_LINES || normalized.length > 200) {
254
+ this._pasteCounter += 1;
255
+ const tag = `[Pasted ~${lineCount} lines]`;
256
+ this._attachments.push({ kind: "pasted-text", tag, text: normalized, lineCount });
257
+ this.insert(`${tag} `);
258
+ }
259
+ else {
260
+ // Short text: insert inline
261
+ this.insert(normalized);
262
+ }
263
+ });
264
+ }
265
+ /**
266
+ * Attach a file. For images, creates an image attachment; for text,
267
+ * creates a file reference. Inserts `[File: name]` tag.
268
+ */
269
+ attachFile(filePath, content, mimeType) {
270
+ this.recordEdit(() => {
271
+ const basename = filePath.split("/").pop() ?? filePath;
272
+ if (mimeType.startsWith("image/")) {
273
+ const data = typeof content === "string"
274
+ ? content
275
+ : Buffer.from(content).toString("base64");
276
+ this._imageCounter += 1;
277
+ const tag = `[Image ${this._imageCounter}: ${basename}]`;
278
+ this._attachments.push({
279
+ kind: "file",
280
+ tag,
281
+ path: filePath,
282
+ image: { type: "image", data, mimeType },
283
+ });
284
+ this.insert(`${tag} `);
285
+ }
286
+ else {
287
+ const tag = `[File: ${basename}]`;
288
+ this._attachments.push({
289
+ kind: "file",
290
+ tag,
291
+ path: filePath,
292
+ content: typeof content === "string" ? content : Buffer.from(content).toString("utf-8"),
293
+ });
294
+ this.insert(`${tag} `);
295
+ }
296
+ });
297
+ }
298
+ /**
299
+ * Remove the attachment whose tag is at or near the cursor.
300
+ * Returns true if an attachment was removed.
301
+ */
302
+ removeAttachmentAtCursor() {
303
+ let removed = false;
304
+ this.recordEdit(() => {
305
+ for (const range of this.attachmentTagRanges()) {
306
+ if (this._cursor >= range.start && this._cursor <= range.removeEnd) {
307
+ this.removeAttachmentRange(range);
308
+ removed = true;
309
+ return;
310
+ }
311
+ }
312
+ });
313
+ return removed;
314
+ }
315
+ // ── cursor movement ─────────────────────────────────────────────
316
+ moveLeft() { this.clearSelection(); this.clearScrollOffset(); if (this._cursor > 0)
317
+ this._cursor -= 1; }
318
+ moveRight() { this.clearSelection(); this.clearScrollOffset(); if (this._cursor < this._text.length)
319
+ this._cursor += 1; }
320
+ moveUp() {
321
+ this.clearSelection();
322
+ this.clearScrollOffset();
323
+ const pos = this.offsetToRowCol(this._cursor);
324
+ if (pos.row <= 0) {
325
+ this._cursor = 0;
326
+ return;
327
+ }
328
+ this._cursor = this.rowColToOffset(pos.row - 1, pos.col);
329
+ }
330
+ moveDown() {
331
+ this.clearSelection();
332
+ this.clearScrollOffset();
333
+ const pos = this.offsetToRowCol(this._cursor);
334
+ const lines = this.logicalLines();
335
+ if (pos.row >= lines.length - 1) {
336
+ this._cursor = this._text.length;
337
+ return;
338
+ }
339
+ this._cursor = this.rowColToOffset(pos.row + 1, pos.col);
340
+ }
341
+ moveToLineStart() { this.clearSelection(); this.clearScrollOffset(); this._cursor = this.findLineStart(this._cursor); }
342
+ moveToLineEnd() { this.clearSelection(); this.clearScrollOffset(); this._cursor = this.findLineEnd(this._cursor); }
343
+ moveToStart() { this.clearSelection(); this.clearScrollOffset(); this._cursor = 0; }
344
+ moveToEnd() { this.clearSelection(); this.clearScrollOffset(); this._cursor = this._text.length; }
345
+ moveWordLeft() { this.clearSelection(); this.clearScrollOffset(); this._cursor = this.findWordStart(this._cursor); }
346
+ moveWordRight() { this.clearSelection(); this.clearScrollOffset(); this._cursor = this.findWordEnd(this._cursor); }
347
+ setCursor(offset, options = {}) {
348
+ this.clearSelection();
349
+ if (!options.preserveScroll)
350
+ this.clearScrollOffset();
351
+ this._cursor = offset;
352
+ this.clampCursor();
353
+ }
354
+ // ── selection ───────────────────────────────────────────────────
355
+ moveLeftExtend() {
356
+ this.clearScrollOffset();
357
+ this.extendSelection();
358
+ if (this._cursor > 0)
359
+ this._cursor -= 1;
360
+ this.syncSelectionActive();
361
+ }
362
+ moveRightExtend() {
363
+ this.clearScrollOffset();
364
+ this.extendSelection();
365
+ if (this._cursor < this._text.length)
366
+ this._cursor += 1;
367
+ this.syncSelectionActive();
368
+ }
369
+ moveUpExtend() {
370
+ this.clearScrollOffset();
371
+ this.extendSelection();
372
+ const pos = this.offsetToRowCol(this._cursor);
373
+ if (pos.row <= 0) {
374
+ this._cursor = 0;
375
+ this.syncSelectionActive();
376
+ return;
377
+ }
378
+ this._cursor = this.rowColToOffset(pos.row - 1, pos.col);
379
+ this.syncSelectionActive();
380
+ }
381
+ moveDownExtend() {
382
+ this.clearScrollOffset();
383
+ this.extendSelection();
384
+ const pos = this.offsetToRowCol(this._cursor);
385
+ const lines = this.logicalLines();
386
+ if (pos.row >= lines.length - 1) {
387
+ this._cursor = this._text.length;
388
+ this.syncSelectionActive();
389
+ return;
390
+ }
391
+ this._cursor = this.rowColToOffset(pos.row + 1, pos.col);
392
+ this.syncSelectionActive();
393
+ }
394
+ moveToLineStartExtend() {
395
+ this.clearScrollOffset();
396
+ this.extendSelection();
397
+ this._cursor = this.findLineStart(this._cursor);
398
+ this.syncSelectionActive();
399
+ }
400
+ moveToLineEndExtend() {
401
+ this.clearScrollOffset();
402
+ this.extendSelection();
403
+ this._cursor = this.findLineEnd(this._cursor);
404
+ this.syncSelectionActive();
405
+ }
406
+ getSelectedText() {
407
+ if (!this.hasSelection)
408
+ return undefined;
409
+ const [start, end] = this.orderedSelection();
410
+ return this._text.slice(start, end);
411
+ }
412
+ deleteSelection() {
413
+ this.recordEdit(() => {
414
+ if (!this.hasSelection)
415
+ return;
416
+ const [selectionStart, selectionEnd] = this.orderedSelection();
417
+ const { start, end, attachmentIndexes } = this.expandRangeToAttachmentBoundaries(selectionStart, selectionEnd);
418
+ this._text = this._text.slice(0, start) + this._text.slice(end);
419
+ for (const index of [...attachmentIndexes].sort((a, b) => b - a)) {
420
+ this._attachments.splice(index, 1);
421
+ }
422
+ this._cursor = start;
423
+ this.clearSelection();
424
+ });
425
+ }
426
+ clearSelection() { this._selection = undefined; }
427
+ selectAll() {
428
+ if (this._text.length === 0)
429
+ return;
430
+ this._selection = { anchor: 0, active: this._text.length };
431
+ }
432
+ // ── bracketed paste ─────────────────────────────────────────────
433
+ beginBracketedPaste() { this._bracketedPasteDepth += 1; }
434
+ endBracketedPaste() { this._bracketedPasteDepth = Math.max(0, this._bracketedPasteDepth - 1); }
435
+ get isInBracketedPaste() { return this._bracketedPasteDepth > 0; }
436
+ // ── rendering ───────────────────────────────────────────────────
437
+ render(width, maxRows, firstPrefix, continuationPrefix) {
438
+ const allVisual = [];
439
+ const logicalLines = this.logicalLines();
440
+ const tagPatterns = this.getTagPatterns();
441
+ for (let i = 0; i < logicalLines.length; i++) {
442
+ const line = logicalLines[i];
443
+ const prefix = i === 0 && allVisual.length === 0 ? firstPrefix : continuationPrefix;
444
+ this.pushVisualLines(allVisual, line, prefix, continuationPrefix, width, i === 0 && allVisual.length === 0, tagPatterns);
445
+ }
446
+ if (allVisual.length === 0) {
447
+ allVisual.push({ text: firstPrefix, wrapped: false, tagSpans: [] });
448
+ }
449
+ const cursorVisualRow = this.computeCursorVisualRow(allVisual, width, firstPrefix, continuationPrefix);
450
+ const safeMaxRows = Math.max(1, maxRows);
451
+ let autoScrollOffset = 0;
452
+ if (allVisual.length > safeMaxRows) {
453
+ if (cursorVisualRow >= safeMaxRows) {
454
+ autoScrollOffset = cursorVisualRow - safeMaxRows + 1;
455
+ }
456
+ autoScrollOffset = Math.min(autoScrollOffset, Math.max(0, allVisual.length - safeMaxRows));
457
+ }
458
+ const maxScrollOffset = Math.max(0, allVisual.length - safeMaxRows);
459
+ const scrollOffset = Math.max(0, Math.min(maxScrollOffset, this._scrollOffset ?? autoScrollOffset));
460
+ const cursorVisible = cursorVisualRow >= scrollOffset && cursorVisualRow < scrollOffset + safeMaxRows;
461
+ const cursorScreenCol = this.computeCursorScreenCol(width, firstPrefix, continuationPrefix);
462
+ return { visualLines: allVisual, cursorVisualRow, cursorScreenCol, scrollOffset, cursorVisible };
463
+ }
464
+ scrollByVisualLines(delta, width, maxRows, firstPrefix, continuationPrefix) {
465
+ const rendered = this.render(width, maxRows, firstPrefix, continuationPrefix);
466
+ return this.setVisualScrollOffset(rendered.scrollOffset + delta, width, maxRows, firstPrefix, continuationPrefix);
467
+ }
468
+ setVisualScrollOffset(offset, width, maxRows, firstPrefix, continuationPrefix) {
469
+ const rendered = this.render(width, maxRows, firstPrefix, continuationPrefix);
470
+ const maxScrollOffset = Math.max(0, rendered.visualLines.length - Math.max(1, maxRows));
471
+ if (maxScrollOffset <= 0) {
472
+ const changed = this._scrollOffset !== undefined;
473
+ this.clearScrollOffset();
474
+ return changed;
475
+ }
476
+ const nextOffset = Math.max(0, Math.min(maxScrollOffset, offset));
477
+ if (nextOffset === rendered.scrollOffset && this._scrollOffset !== undefined)
478
+ return false;
479
+ this._scrollOffset = nextOffset;
480
+ return true;
481
+ }
482
+ /** Convert a rendered visual row and 1-based terminal column into a text cursor offset. */
483
+ offsetAtVisualPosition(visualRow, screenColumn, width, firstPrefix, continuationPrefix) {
484
+ const targetVisualRow = Math.max(0, visualRow);
485
+ const targetColumn = Math.max(1, screenColumn);
486
+ const logicalLines = this.logicalLines();
487
+ let currentVisualRow = 0;
488
+ let logicalOffset = 0;
489
+ for (let logicalRow = 0; logicalRow < logicalLines.length; logicalRow += 1) {
490
+ const line = logicalLines[logicalRow];
491
+ let currentPrefix = logicalRow === 0 ? firstPrefix : continuationPrefix;
492
+ if (line.length === 0) {
493
+ if (currentVisualRow === targetVisualRow)
494
+ return logicalOffset;
495
+ currentVisualRow += 1;
496
+ logicalOffset += logicalRow < logicalLines.length - 1 ? 1 : 0;
497
+ continue;
498
+ }
499
+ let chunkStart = 0;
500
+ while (chunkStart < line.length) {
501
+ const available = Math.max(1, width - currentPrefix.length);
502
+ const chunkEnd = Math.min(line.length, chunkStart + available);
503
+ if (currentVisualRow === targetVisualRow) {
504
+ const columnInChunk = Math.max(0, Math.min(chunkEnd - chunkStart, targetColumn - currentPrefix.length - 1));
505
+ return logicalOffset + chunkStart + columnInChunk;
506
+ }
507
+ currentVisualRow += 1;
508
+ chunkStart = chunkEnd;
509
+ currentPrefix = continuationPrefix;
510
+ }
511
+ logicalOffset += line.length + (logicalRow < logicalLines.length - 1 ? 1 : 0);
512
+ }
513
+ return this._text.length;
514
+ }
515
+ // ── private helpers ─────────────────────────────────────────────
516
+ recordEdit(mutator) {
517
+ if (this._restoringHistory || this._historyMutationDepth > 0) {
518
+ mutator();
519
+ return;
520
+ }
521
+ const before = this.captureHistorySnapshot();
522
+ this._historyMutationDepth += 1;
523
+ try {
524
+ mutator();
525
+ }
526
+ finally {
527
+ this._historyMutationDepth -= 1;
528
+ }
529
+ if (!this.didContentChangeFrom(before))
530
+ return;
531
+ this.clearScrollOffset();
532
+ if (!this.isRecordableHistorySnapshot(before)) {
533
+ this.clearHistory();
534
+ return;
535
+ }
536
+ this._undoStack.push(before);
537
+ this._redoStack.length = 0;
538
+ this.trimHistory();
539
+ }
540
+ captureHistorySnapshot() {
541
+ return {
542
+ text: this._text,
543
+ cursor: this._cursor,
544
+ selection: this._selection ? { ...this._selection } : undefined,
545
+ attachments: [...this._attachments],
546
+ imageCounter: this._imageCounter,
547
+ pasteCounter: this._pasteCounter,
548
+ };
549
+ }
550
+ restoreHistorySnapshot(snapshot) {
551
+ this._restoringHistory = true;
552
+ try {
553
+ this._text = snapshot.text;
554
+ this._cursor = snapshot.cursor;
555
+ this._selection = snapshot.selection ? { ...snapshot.selection } : undefined;
556
+ this._attachments.length = 0;
557
+ this._attachments.push(...snapshot.attachments);
558
+ this._imageCounter = snapshot.imageCounter;
559
+ this._pasteCounter = snapshot.pasteCounter;
560
+ this.clearScrollOffset();
561
+ this.clampCursor();
562
+ }
563
+ finally {
564
+ this._restoringHistory = false;
565
+ }
566
+ }
567
+ didContentChangeFrom(snapshot) {
568
+ return this._text !== snapshot.text || !this.sameAttachmentRefs(snapshot.attachments);
569
+ }
570
+ sameAttachmentRefs(attachments) {
571
+ if (this._attachments.length !== attachments.length)
572
+ return false;
573
+ return this._attachments.every((attachment, index) => attachment === attachments[index]);
574
+ }
575
+ isRecordableHistorySnapshot(snapshot) {
576
+ return snapshot.text.length <= INPUT_UNDO_MAX_SNAPSHOT_TEXT_CHARS
577
+ && snapshot.attachments.length <= INPUT_UNDO_MAX_SNAPSHOT_ATTACHMENT_REFS;
578
+ }
579
+ clearHistory() {
580
+ this._undoStack.length = 0;
581
+ this._redoStack.length = 0;
582
+ }
583
+ clearScrollOffset() {
584
+ this._scrollOffset = undefined;
585
+ }
586
+ trimHistory() {
587
+ while (this._undoStack.length > INPUT_UNDO_MAX_STATES)
588
+ this._undoStack.shift();
589
+ while (this._redoStack.length > INPUT_UNDO_MAX_STATES)
590
+ this._redoStack.shift();
591
+ while (this.totalHistoryTextChars() > INPUT_UNDO_MAX_TEXT_CHARS
592
+ || this.totalHistoryAttachmentRefs() > INPUT_UNDO_MAX_ATTACHMENT_REFS) {
593
+ if (this._undoStack.length > 0)
594
+ this._undoStack.shift();
595
+ else if (this._redoStack.length > 0)
596
+ this._redoStack.shift();
597
+ else
598
+ break;
599
+ }
600
+ }
601
+ totalHistoryTextChars() {
602
+ return this.historyTextChars(this._undoStack) + this.historyTextChars(this._redoStack);
603
+ }
604
+ totalHistoryAttachmentRefs() {
605
+ return this.historyAttachmentRefs(this._undoStack) + this.historyAttachmentRefs(this._redoStack);
606
+ }
607
+ historyTextChars(snapshots) {
608
+ let total = 0;
609
+ for (const snapshot of snapshots)
610
+ total += snapshot.text.length;
611
+ return total;
612
+ }
613
+ historyAttachmentRefs(snapshots) {
614
+ let total = 0;
615
+ for (const snapshot of snapshots)
616
+ total += snapshot.attachments.length;
617
+ return total;
618
+ }
619
+ clampCursor() {
620
+ this._cursor = Math.max(0, Math.min(this._text.length, this._cursor));
621
+ }
622
+ extendSelection() {
623
+ if (!this._selection) {
624
+ this._selection = { anchor: this._cursor, active: this._cursor };
625
+ }
626
+ }
627
+ syncSelectionActive() {
628
+ if (this._selection) {
629
+ this._selection = { anchor: this._selection.anchor, active: this._cursor };
630
+ }
631
+ }
632
+ orderedSelection() {
633
+ if (!this._selection)
634
+ return [this._cursor, this._cursor];
635
+ const a = this._selection.anchor;
636
+ const b = this._selection.active;
637
+ return a < b ? [a, b] : [b, a];
638
+ }
639
+ removeAttachmentForBackwardDelete() {
640
+ for (const range of this.attachmentTagRanges()) {
641
+ // Backspace immediately after a tag (or its trailing space), or inside a
642
+ // tag, removes the whole virtual tag and its attachment atomically.
643
+ if (this._cursor > range.start && this._cursor <= range.removeEnd) {
644
+ this.removeAttachmentRange(range);
645
+ return true;
646
+ }
647
+ }
648
+ return false;
649
+ }
650
+ removeAttachmentForForwardDelete() {
651
+ for (const range of this.attachmentTagRanges()) {
652
+ // Delete at the start of a tag, or inside it, removes the whole virtual
653
+ // tag and its attachment atomically.
654
+ if (this._cursor >= range.start && this._cursor < range.end) {
655
+ this.removeAttachmentRange(range);
656
+ return true;
657
+ }
658
+ }
659
+ return false;
660
+ }
661
+ removeAttachmentRange(range) {
662
+ this._text = this._text.slice(0, range.start) + this._text.slice(range.removeEnd);
663
+ this._cursor = range.start;
664
+ this._attachments.splice(range.attachmentIndex, 1);
665
+ }
666
+ attachmentTagRanges() {
667
+ const ranges = [];
668
+ for (let attachmentIndex = 0; attachmentIndex < this._attachments.length; attachmentIndex += 1) {
669
+ const tag = this._attachments[attachmentIndex].tag;
670
+ const start = this._text.indexOf(tag);
671
+ if (start < 0)
672
+ continue;
673
+ const end = start + tag.length;
674
+ const removeEnd = this._text[end] === " " ? end + 1 : end;
675
+ ranges.push({ attachmentIndex, start, end, removeEnd });
676
+ }
677
+ return ranges.sort((a, b) => a.start - b.start);
678
+ }
679
+ expandRangeToAttachmentBoundaries(start, end) {
680
+ let expandedStart = start;
681
+ let expandedEnd = end;
682
+ const attachmentIndexes = new Set();
683
+ let changed = true;
684
+ while (changed) {
685
+ changed = false;
686
+ for (const range of this.attachmentTagRanges()) {
687
+ const overlaps = expandedStart < range.removeEnd && expandedEnd > range.start;
688
+ if (!overlaps)
689
+ continue;
690
+ attachmentIndexes.add(range.attachmentIndex);
691
+ if (range.start < expandedStart) {
692
+ expandedStart = range.start;
693
+ changed = true;
694
+ }
695
+ if (range.removeEnd > expandedEnd) {
696
+ expandedEnd = range.removeEnd;
697
+ changed = true;
698
+ }
699
+ }
700
+ }
701
+ return { start: expandedStart, end: expandedEnd, attachmentIndexes };
702
+ }
703
+ logicalLines() { return this._text.split("\n"); }
704
+ findLineStart(offset) {
705
+ const idx = this._text.lastIndexOf("\n", offset - 1);
706
+ return idx < 0 ? 0 : idx + 1;
707
+ }
708
+ findLineEnd(offset) {
709
+ const idx = this._text.indexOf("\n", offset);
710
+ return idx < 0 ? this._text.length : idx;
711
+ }
712
+ findWordStart(offset) {
713
+ let pos = offset;
714
+ while (pos > 0 && /\s/.test(this._text[pos - 1]))
715
+ pos -= 1;
716
+ while (pos > 0 && /\S/.test(this._text[pos - 1]))
717
+ pos -= 1;
718
+ return pos;
719
+ }
720
+ findWordEnd(offset) {
721
+ let pos = offset;
722
+ while (pos < this._text.length && /\s/.test(this._text[pos]))
723
+ pos += 1;
724
+ while (pos < this._text.length && /\S/.test(this._text[pos]))
725
+ pos += 1;
726
+ return pos;
727
+ }
728
+ offsetToRowCol(offset) {
729
+ const lines = this.logicalLines();
730
+ let remaining = offset;
731
+ for (let row = 0; row < lines.length; row++) {
732
+ const line = lines[row];
733
+ if (remaining <= line.length)
734
+ return { row, col: remaining };
735
+ remaining -= line.length + 1;
736
+ }
737
+ const lastRow = lines.length - 1;
738
+ return { row: lastRow, col: lines[lastRow].length };
739
+ }
740
+ rowColToOffset(row, col) {
741
+ const lines = this.logicalLines();
742
+ let offset = 0;
743
+ for (let r = 0; r < lines.length; r++) {
744
+ if (r === row)
745
+ return offset + Math.min(col, lines[r].length);
746
+ offset += lines[r].length + 1;
747
+ }
748
+ return this._text.length;
749
+ }
750
+ /** Get regex patterns for all attachment tags in the text. */
751
+ getTagPatterns() {
752
+ return this._attachments.map((att) => ({
753
+ tag: att.tag,
754
+ regex: new RegExp(escapeRegex(att.tag), "g"),
755
+ }));
756
+ }
757
+ /** Find tag spans within a text string (offsets relative to the text). */
758
+ findTagSpans(text, tagPatterns) {
759
+ const spans = [];
760
+ for (const { regex } of tagPatterns) {
761
+ regex.lastIndex = 0;
762
+ let match;
763
+ while ((match = regex.exec(text)) !== null) {
764
+ spans.push({ start: match.index, end: match.index + match[0].length });
765
+ }
766
+ }
767
+ return spans;
768
+ }
769
+ pushVisualLines(output, logicalLine, firstPrefix, continuationPrefix, width, isFirst, tagPatterns) {
770
+ let currentPrefix = firstPrefix;
771
+ let remaining = logicalLine;
772
+ if (remaining.length === 0) {
773
+ output.push({ text: currentPrefix, wrapped: !isFirst || output.length > 0, tagSpans: [] });
774
+ return;
775
+ }
776
+ let first = true;
777
+ while (remaining.length > 0) {
778
+ const available = Math.max(1, width - currentPrefix.length);
779
+ const chunk = remaining.slice(0, available);
780
+ const fullText = `${currentPrefix}${chunk}`;
781
+ // Compute tag spans adjusted for prefix offset
782
+ const spans = this.findTagSpans(chunk, tagPatterns).map((s) => ({
783
+ start: s.start + currentPrefix.length,
784
+ end: s.end + currentPrefix.length,
785
+ }));
786
+ output.push({ text: fullText, wrapped: !first, tagSpans: spans });
787
+ remaining = remaining.slice(chunk.length);
788
+ currentPrefix = continuationPrefix;
789
+ first = false;
790
+ }
791
+ }
792
+ computeCursorVisualRow(allVisual, width, firstPrefix, continuationPrefix) {
793
+ const pos = this.offsetToRowCol(this._cursor);
794
+ const logicalLines = this.logicalLines();
795
+ let visualRow = 0;
796
+ for (let logRow = 0; logRow < logicalLines.length; logRow++) {
797
+ const line = logicalLines[logRow];
798
+ const prefix = logRow === 0 ? firstPrefix : continuationPrefix;
799
+ const prefixLen = prefix.length;
800
+ const available = Math.max(1, width - prefixLen);
801
+ if (line.length === 0) {
802
+ if (logRow === pos.row)
803
+ return visualRow;
804
+ visualRow += 1;
805
+ continue;
806
+ }
807
+ const wrappedCount = Math.ceil(line.length / available);
808
+ for (let w = 0; w < wrappedCount; w++) {
809
+ const chunkStart = w * available;
810
+ if (logRow === pos.row && pos.col >= chunkStart && (w === wrappedCount - 1 || pos.col < chunkStart + available)) {
811
+ return visualRow;
812
+ }
813
+ visualRow += 1;
814
+ }
815
+ }
816
+ return Math.max(0, allVisual.length - 1);
817
+ }
818
+ computeCursorScreenCol(width, firstPrefix, continuationPrefix) {
819
+ const pos = this.offsetToRowCol(this._cursor);
820
+ const prefixLen = pos.row === 0 ? firstPrefix.length : continuationPrefix.length;
821
+ const available = Math.max(1, width - prefixLen);
822
+ const colInChunk = available > 0 ? pos.col % available : pos.col;
823
+ return prefixLen + colInChunk + 1;
824
+ }
825
+ }
826
+ // ── Utility ─────────────────────────────────────────────────────────
827
+ function escapeRegex(str) {
828
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
829
+ }
830
+ function removeVirtualAttachmentTag(text, tag) {
831
+ const withTrailingSpace = `${tag} `;
832
+ if (text.includes(withTrailingSpace))
833
+ return text.replace(withTrailingSpace, "");
834
+ return text.replace(tag, "");
835
+ }